Deferred Deep Linking Without an SDK: How It Actually Works Under the Hood
You can carry link intent across an app install without embedding an attribution SDK. Here is what actually survives the store boundary on Android and iOS, what breaks, and what each workaround costs in privacy.
The context dies at the store boundary
Someone taps a link to /product/42. The app is not installed, so your redirect sends them to the store. They install, they open the app, and they land on a generic home screen. The HTTP request that carried their intent ended at the store listing. The freshly installed app starts with no URL, no cookie, no referrer header. Nothing from the click survived.
Carrying that context across the install is deferred deep linking, and the standard answer is "embed an MMP SDK". A lot of teams do not want that answer. The question shows up wherever developers gather — Apple Developer Forums thread 772811 is one of many asking how to do it without a third-party SDK — and the audience got wider in August 2026, when AppsFlyer moved deferred deep linking out of its free plan. So: can you build it yourself? On Android, yes, cleanly and officially. On iOS, sort of, through workarounds with real costs.
One scope note before the mechanics. We cover what deferred deep linking is and when it pays off in a separate article. This post is only about the plumbing: which transport can smuggle a token across an install, and how much you can trust each one.
What survives the install, and what does not
Android: the Install Referrer API is the honest good news
Google Play solves this natively. A Play store URL accepts a referrer query parameter, Play stores that string alongside the install, and the app can read it back verbatim after first launch through the Install Referrer API. That is the entire mechanism. No fingerprinting, no clipboard, no guessing.
It is not quite "zero code", to be precise. Reading the value requires the com.android.installreferrer library — a small artifact that talks locally to the Play Store app on the device. It makes no network calls to anyone, it is not an analytics SDK phoning home, and it adds nothing to your privacy disclosures beyond what you choose to do with the string.
What goes in the parameter is up to you: a link ID, a campaign name, an encoded deep-link target. Keep it short and URL-encoded — the referrer on play.google.com/store/apps/details?id=com.example&referrer=tok_8f2 comes back exactly as sent.
The full loop, end to end
- 1On link tap, your redirect appends referrer=<token> to the Play URL, where the token points at click context stored on your server.
- 2On first launch, the app connects an InstallReferrerClient and reads the installReferrer string.
- 3The app exchanges the token at your API for the stored context — deep-link path, campaign — and routes the user.
- 4Your server marks the token consumed. The referrer string persists on the device, so single-use enforcement has to live server-side.
The limits are narrow and predictable: the API only covers installs that actually went through Google Play. Sideloads, Huawei AppGallery, and other stores do not carry a Play referrer, and the value should be read once shortly after first launch, not treated as a permanent store.
Appy links already do the Android half
Since September 2026, every Appy link that sends someone to a Google Play listing adds the referrer parameter by itself. There is nothing to configure and no SDK to add. The referrer always carries appy_slug, the slug of the link that was tapped, so the app knows which link produced the install. That part works on every plan, Free included.
If the link has parameter forwarding switched on, the query string from the tap goes into the referrer as well. A QR code pointing at appy.to/summer?screen=product-42&utm_source=poster reaches first launch as the string below. Nothing is stored on our side for this, and nothing is matched by guesswork.
- Scanned or tapped
- https://appy.to/summer?screen=product-42&utm_source=poster
- Sent to Google Play
- https://play.google.com/store/apps/details?id=com.example.app&referrer=appy_slug%3Dsummer%26screen%3Dproduct-42%26utm_source%3Dposter&screen=product-42&utm_source=poster
- Read by the app on first launch
- appy_slug=summer&screen=product-42&utm_source=poster
The dashboard setting
Parameter forwarding is a per-link switch on the Business plan. When you create a link it is the “Enable parameter forwarding” toggle; on an existing link the same setting is called “Forward incoming parameters”. With it off, the referrer still carries appy_slug and nothing else.
A few rules worth knowing. Anything you wrote into the Play URL yourself wins: if the destination already contains referrer=utm_source%3Dposter, Appy keeps that value and adds appy_slug next to it. appy_slug itself can’t be overwritten from the query string, so one link can’t claim another link’s installs. And if forwarded parameters would push the referrer past 1 KB, Appy drops them and keeps only the slug.
Parameters travel in the clear. That is fine for a campaign name or a screen ID. For anything personal, or more than a handful of values, forward a short token and resolve it on your server, following the pattern further down.
Reading it in the app
Add the Install Referrer library, read the value once on first launch, and parse it like a query string. That is the whole client side:
implementation("com.android.installreferrer:installreferrer:2.2")fun readAppyReferrer(context: Context, onResult: (Map<String, String>) -> Unit) {
val client = InstallReferrerClient.newBuilder(context).build()
client.startConnection(object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) {
val raw = if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) {
runCatching { client.installReferrer.installReferrer }.getOrNull()
} else null
client.endConnection()
val uri = Uri.parse("?" + raw.orEmpty())
onResult(uri.queryParameterNames.associateWith { uri.getQueryParameter(it).orEmpty() })
}
override fun onInstallReferrerServiceDisconnected() = Unit
})
}
readAppyReferrer(this) { params ->
val slug = params["appy_slug"] ?: return@readAppyReferrer
params["screen"]?.let { openScreen(it) }
analytics.logInstallSource(slug, params["utm_source"])
}Run it only on the first launch after install, and remember that you did. The referrer stays on the device, so reading it on every cold start would keep sending people to the same screen. Installs from AppGallery or a sideloaded APK come back without a referrer; for those, just open the normal home screen.
iOS: no official equivalent, three workarounds ranked
Apple ships nothing like the Install Referrer. An App Store product page does not carry arbitrary parameters through an install, and everything below is a workaround built on that absence. Ranked from least bad to most niche:
Clipboard pass-through
01 · medium reliability, permission-gatedYour link-tap page writes a short token to the clipboard — behind a button tap, since Safari requires a user gesture for programmatic copy — then forwards to the App Store. On first open, the app reads the pasteboard, finds the token, and exchanges it with your server.
The catch arrived with iOS 16: reading the pasteboard triggers the system paste prompt. The user sees "Allow this app to paste from Safari?", on the very first screen of an app they installed thirty seconds ago, and many decline. A decline is silent loss — you cannot distinguish it from "no token existed". UIPasteboard.detectPatterns can check for a matching pattern without prompting, but it cannot read the value. The clipboard can also be overwritten between tap and first open. It works, measurably often, but it is a coin with a visible second face.
Probabilistic matching, done honestly
02 · a scored guess, never a certaintyAt click time the server keeps a short record: which link, which platform, the OS version from the user agent, a timestamp, and a way to recognize the network the click came from. On first open the app asks for a recent click that looks like the same phone on the same network. No token travels at all. The match is a guess, and the only real question is whether it admits that.
Built carelessly, it deserves its bad name: raw IP addresses kept for days, the best candidate returned as if it were certain, and a stranger on the same office Wi-Fi able to claim someone else’s referral. The signals are also weaker than they look. iCloud Private Relay hides addresses, carrier NAT puts many phones behind one, Chrome on Android reports every phone as Android 10, and Safari on iOS 26 freezes its version at 18.6.
Built carefully, it is still a guess, but a useful one. Score every candidate and return the score with the signals behind it. Never let a guess reach certainty. Let the app choose its threshold, and don’t use up a click that scored below it, so the phone that really clicked can still claim it. Keep the window short and store no IP address. Appy’s Enterprise SDK keeps that work on Appy’s side. Your app gets the link or nothing, your backend can check whether an install was verified, and networks are recognized through a daily-rotating keyed hash of the address that expires after two hours. With strict attribution on, only verified installs count, and everything else is organic.
App Clips and other niche paths
03 · reliable but narrowAn App Clip invoked from your link receives the full URL and can hand the context to the full app through a shared container after install. Inside its niche this is the most reliable iOS path — but it requires building and maintaining an App Clip, and it only fits flows where a lightweight instant experience makes sense. Shared web credentials and associated domains solve the adjacent problem of auth continuity, not general link context. Worth knowing, rarely the answer.
The design pattern that holds regardless of mechanism
Whichever transport carries the token — referrer string, clipboard, even a probabilistic match ID — the server-side architecture is the same, and getting it right matters more than the transport. Do not stuff the whole deep link into the channel. Store the click context on your server and move only a short-lived key.
Store context keyed by a random token
On click, write {deep_link, campaign, timestamp} under a short random token, and put only the token into the transport. This keeps payloads small, keeps campaign data out of clipboards and referrer logs, and lets you change what "context" means without touching the client.
Expire aggressively
A TTL of 30 to 60 minutes covers the real tap-to-install journey. A deferred route resolved a week after the click is wrong more often than it is right — the person who opens the app then is not on the errand they were on when they tapped.
Enforce single use
Consume the token on first exchange. Referrer strings persist on-device and clipboards can be read twice; without single-use, a reinstall or a second device can replay someone else’s context and misattribute the install.
Fail to the default, not to a guess
No token, expired token, declined paste prompt — land the user on the normal home screen. A generic first open is mildly disappointing; being routed into another user’s cart is a support ticket.
Reliability, honestly stated
Rough figures from teams running these in production. Your mix of traffic sources, install delay, and audience iOS version moves every number.
| Mechanism | Platform | Reliability | Notes |
|---|---|---|---|
| Play Install Referrer | Android | High | Official API, survives delayed installs, no user prompt. Play installs only. |
| Clipboard token | iOS | Medium | Gated by the iOS 16+ paste prompt; declines are silent; clipboard is volatile. |
| Scored probabilistic match | iOS; Android without a referrer | Medium, scored | Same network, OS version and timing. Strong on one Wi-Fi minutes after the tap; weak across networks, CGNAT and Private Relay. Worth using only with a visible score and a threshold. |
| App Clip hand-off | iOS | High, narrow | Deterministic, but only for flows that justify building an App Clip. |
What this means for your stack
If your deferred routing need is Android-only, you need very little: a link that appends the referrer parameter, the small referrer library, and one endpoint that exchanges tokens. That is an afternoon of work, and it is genuinely SDK-free in the sense that matters — no third-party code observing your users.
iOS is where managed solutions earn their keep, because there the workarounds are the product: matching infrastructure, a scoring model someone has to keep honest, and every OS release that moves the ground, like iOS 26 freezing Safari’s version string. That is why Appy’s iOS and Android SDK, with deferred deep linking and install attribution, sits on the Enterprise plan, while the Android referrer comes with every link.
Be honest about which problem you have, though. If what you actually need is "users who already have the app should land on the right screen", that is standard deep linking — no install boundary to cross. Appy’s deep link shim covers that case on the Pro plan at $9.99/month, sends people without the app to the store, and still needs no SDK. Measure how much of your funnel is genuinely fresh-install-with-context before buying the harder machinery.
Read next
Frequently asked questions
Does the Install Referrer work from a QR scan?
Yes, provided the QR resolves through a link that appends the referrer parameter to the Play URL. The mechanism does not care how the click started — scan, tap, or NFC — only that the store URL carried the referrer when Play opened. A QR pointing straight at a bare Play URL carries nothing.
Why did my clipboard token approach suddenly break?
Almost certainly the iOS paste prompt. Since iOS 16, the first pasteboard read from your app triggers a system permission dialog, and a meaningful share of users decline it. Your code did not regress; your read now has a human in the loop. Check whether your token-found rate dropped rather than went to zero — that is the signature.
Is probabilistic matching allowed on iOS?
Apple’s developer agreement forbids deriving data from a device to uniquely identify it, and App Review can reject apps whose SDKs do. A fingerprint that recognizes a phone across apps and weeks is on the wrong side of that line. Comparing one click and one first launch on the same network within two hours, for your own link, with no stored IP and a score attached, is a much narrower thing, but you still describe the data in your privacy details. Whatever tool you use, ask what it stores, for how long, and whether it tells you when it is guessing.
Do I need deferred deep linking at all?
Only if the post-install landing screen materially changes your funnel. If most installs come from generic campaigns with no specific destination, a good onboarding beats a deferred route. Measure first: count clicks where a real deep-link target existed and the app was not installed. If that number is small, spend the effort elsewhere.
Continue exploring
Know Which Link Brought Every Install: Appy’s iOS and Android SDK
Appy now follows your links past the app store: installs, in-app events and revenue for every link, QR code and campaign, and new users who open on the right screen.
Chrome Tells Every Server Your Phone Runs Android 10
Chrome on Android reports every phone as Android 10, model K. What that did to deferred deep link matching, why Critical-CH would count every click twice, and how Client Hints bring the real version and model back.
Deferred Deep Linking: How It Works and When to Use It
Understand how deferred deep linking carries the original link intent across install so new users land on the right in-app screen, not a generic home tab.
Looking for something else? Browse all topics on the blog.
Route the installs you already earned
The Play install referrer on every link, plus deep links on Pro that send people without the app to the store, with no SDK in your app. When you need deferred deep links and install attribution on iOS and Android, the Enterprise SDK credits every install to the link that brought it.
Start with Appy