Glossary/Failure modes
Deferred deep link not working
Definition
A deferred deep link fails when the signal meant to carry the pre-install destination across the app store — an install referrer, a stored token, or a server-side match — is absent, expired, or never read on first launch.
Unlike a standard deep link, which either works or does not, deferred deep linking depends on a signal surviving a journey through a store, an install, and a first launch. Each platform provides a different signal with different reliability, and the useful diagnostic question is not "is it broken" but "which signal was this install supposed to use, and did that signal exist". On Android the answer is usually knowable with certainty. On iOS it frequently is not, and that is a property of the platform rather than of your integration.
Which signal was this install relying on?
Start here, because it determines whether the failure is a bug you can fix or a limit you have to design around.
| Install path | Signal | Deterministic? |
|---|---|---|
| Android, from Google Play | Play Install Referrer — a string set on the store URL, readable on first launch | Yes |
| Android, from a Meta ad | Meta Install Referrer, in addition to the Play referrer | Yes |
| Android, sideloaded or pre-installed | None — the install never passed through Play | No signal at all |
| iOS, from the App Store | No general-purpose referrer exists. SKAdNetwork reports installs but carries no destination | No |
| iOS, same-device within a session | A token the web page wrote, read by the app on first launch | Yes, when the token survives |
| iOS, TestFlight | None — TestFlight installs carry nothing | No signal at all |
iOS has no install referrer, and no amount of integration adds one
Android's Play Install Referrer is a first-party, deterministic channel from the store to the app. iOS ships no equivalent. Any iOS deferred deep link is therefore relying on a token that survived the store trip, or on a probabilistic guess. We do not ship fingerprint-based matching to close that gap, so on iOS a lost token means a lost destination — an honest home-screen landing rather than a wrong-screen one.
Android: the referrer did not arrive
The Play Install Referrer is a string you set on the store URL, which Google holds through the install and hands to the app on request. If it is missing, one of four things happened.
# The referrer value must be a SINGLE url-encoded parameter.
# Decoded, this one is:
# utm_source=newsletter&utm_campaign=spring&dl=%2Fproducts%2F42
https://play.google.com/store/apps/details?id=com.example.shop&referrer=utm_source%3Dnewsletter%26utm_campaign%3Dspring%26dl%3D%252Fproducts%252F42
# A double-encoding mistake here is the most common silent failure: the
# inner value needs encoding too, or the & inside it terminates the
# referrer parameter and everything after it is dropped.- The install did not come from a Play URL carrying `referrer`. The user searched for the app in the Play app rather than following your link, or your fallback sent them to a bare store URL with the parameter stripped. The referrer is set by the URL that opened the store listing — nothing else can set it.
- The referrer was double-encoded or truncated. An unencoded
&inside the referrer value terminates the parameter, so everything after the first&is silently discarded. This produces a partial referrer, which is worse than none because it looks like it worked. - The app never asked for it. The referrer is pulled through the Play Install Referrer library, not pushed. The old
INSTALL_REFERRERbroadcast was removed in 2020, and integrations copied from pre-2020 guides listen for a broadcast that will never fire. - The install came from outside Play. Sideloaded APKs, Firebase App Distribution, App Center, pre-installed builds, and most alternative stores carry no Play referrer.
val client = InstallReferrerClient.newBuilder(context).build()
client.startConnection(object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) {
when (responseCode) {
InstallReferrerClient.InstallReferrerResponse.OK -> {
val details = client.installReferrer
// "utm_source=newsletter&utm_campaign=spring&dl=%2Fproducts%2F42"
val referrer = details.installReferrer
// These two timestamps are also the basis of click-injection
// and click-spamming detection.
val clickTime = details.referrerClickTimestampSeconds
val installTime = details.installBeginTimestampSeconds
router.handleDeferred(referrer)
client.endConnection()
}
InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED ->
Log.w(TAG, "Play Store version does not support the referrer API")
InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE ->
Log.w(TAG, "Play Store unreachable — retry later, do not give up")
}
}
override fun onInstallReferrerServiceDisconnected() {
// Transient. Retry — do not treat as "no referrer".
}
})`SERVICE_UNAVAILABLE` is not an answer
Treating a disconnection as "this install had no referrer" throws away recoverable installs. The referrer stays available for 90 days after install, so a failed first attempt should be retried on a later launch rather than resolved as empty.
The causes that are not platform-specific
You are testing on a device that has installed the app before. Deferred deep linking fires on *first* launch after install. A reinstall on a device that has run the app is not a first launch in the sense that matters — Android may still return a cached referrer from the original install, and iOS will find leftover state. Test on a device that has genuinely never had the app, or reset it fully.
The app was already installed. If the user already has the app, there is no deferred flow at all — the link opens the app directly through the ordinary Universal Link or App Link path. A destination lost in this case is a routing bug, not a deferred one, and looking at the deferred integration will find nothing.
The SDK initialised after the first screen decided what to show. The deferred payload arrives asynchronously — a service connection on Android, a network call on iOS. If your app has already routed to the home screen by the time it resolves, the destination is technically received and functionally lost. The first screen has to be prepared to be told where to go after it has drawn.
The payload was consumed once and discarded. Deferred context is delivered once. An integration that reads it during a launch that then crashes, or reads it on a code path that runs before the router exists, loses it permanently. Persist it before acting on it.
The match window expired. Any deferred mechanism has a window between click and install after which it stops matching. A tester who clicks a link, gets distracted, and installs the next day falls outside it — and so does a real user, which is a good reason not to treat the window as a formality.
Testing it without a false pass
| Method | Proves | Blind to |
|---|---|---|
| Reinstall on your own device | Little — cached referrer and residual state make it pass wrongly | Everything that matters |
adb shell am broadcast with a fake referrer | Your parsing and routing logic | Whether the real referrer ever arrives |
| Play internal testing track, fresh device | The real Android path end to end | Nothing — this is the real test |
| TestFlight on iOS | Your routing, given a payload | The store trip itself — TestFlight carries no referrer |
| A wiped device or a fresh emulator with Play services | The genuine first-launch path | Nothing — the other honest test |
The reason so many deferred integrations ship broken is that the convenient tests are the misleading ones and the honest tests are slow. Budget for the slow one before release rather than discovering the difference from install data.
deep link debugger
Deferred routing sits on top of the association layer, so a domain that fails verification breaks the deferred path too. The debugger rules that layer out first — assetlinks.json, the association file, redirect chains, and fingerprints — so you are debugging the referrer rather than a broken foundation underneath it.
Frequently asked questions
- Why does my deferred deep link land on the home screen instead of the destination?
- The signal that was supposed to carry the destination across the install did not arrive or was not read in time. On Android that means the Play Install Referrer was missing, truncated by an encoding mistake, or never requested because the integration listens for the removed INSTALL_REFERRER broadcast. On iOS there is no install referrer at all, so the destination depended on a token surviving the store trip. It is also common for the payload to arrive correctly but after the app has already routed to the home screen.
- Does iOS have an install referrer like Android?
- No. Google Play provides the Play Install Referrer, a deterministic first-party channel that carries a string from the store link through to the app's first launch. Apple ships no equivalent. SKAdNetwork reports that an install happened and attributes it to a campaign, but it carries no destination and cannot be used to route a user to a screen.
- Why does my deferred deep link work on my device but not for real users?
- Because reinstalling on a device that has already run the app is not a genuine first launch. Android can return a cached referrer from the original install and iOS finds leftover state, so the test passes for reasons that will not exist for a new user. Test from the Play internal testing track on a device that has never had the app, or on a freshly wiped device.
- How long is the Play Install Referrer available after install?
- 90 days. That means a failed first read is recoverable — if the referrer client returns SERVICE_UNAVAILABLE or disconnects, retrying on a later launch will still get the value. Treating the first failure as proof that the install had no referrer discards attributable installs unnecessarily.
- Why is my install referrer truncated?
- Almost always a double-encoding mistake in the store URL. The referrer parameter must be a single URL-encoded value, so any ampersand inside it needs encoding as %26. An unencoded ampersand terminates the referrer parameter, and everything after it is discarded silently — producing a partial referrer that looks like a successful read.
- Does deferred deep linking work if the app is already installed?
- There is no deferred flow in that case. The link opens the app directly through the normal Universal Link or App Link path, and if the destination is lost it is a routing problem rather than a deferred one. Deferred deep linking only applies to a user who does not yet have the app at the moment they tap.
Related terms
- Deep link opens the browser instead of the app — A deep link opens the browser instead of the app when the operating system has not accepted the app as a verified handler for that URL, or when the tap occurred in a context that never offers the app the chance to handle it.
- Unattributed installs — An install is unattributed when no signal linking it to a prior ad click or link tap survived the journey through the app store, which can mean the install was organic or that the signal existed and was lost.