Deferred links
Links tapped before the app was installed, how the Play Install Referrer and authenticated handoffs bring them to your Android app, and how much to trust them.
A deferred deep link is a link someone tapped before your app was installed. They go to the Play Store, install, open the app, and should land on the screen the link was for instead of the home screen.
On Android this works out of the box and deterministically, thanks to the Play Install Referrer. Lander labels every result with a confidence and a method. This page covers the app side. For the background (what is stored, privacy, iOS) read Deferred deep linking.
At a glance
| Flow | confidence | method | What you do |
|---|---|---|---|
| Play Install Referrer | DETERMINISTIC | install_referrer | Nothing. Works out of the box. |
| Authenticated handoff | DETERMINISTIC | authenticated_handoff | One call from your server, Lander.claimHandoff after sign in. |
| Anything else | UNATTRIBUTED | Nothing. No link is delivered; the install is still counted. |
Probabilistic matching and the Continue in app page are iOS only.
Handle them in your app
Deferred links arrive like every other link: on Lander.deepLinks, or from Lander.getInitialLink(), exactly once. isDeferred is true. The one exception is claimHandoff, which returns its link to you directly.
Lander.deepLinks.collect { link ->
if (link.isDeferred) {
analytics.log("opened_from_install", link.campaign)
}
navigate(link.route)
}Rules the SDK follows for you:
- Only a first launch can produce a deferred link (from the store, or after a reinstall or clearing the app's data, since the install ID does not survive either).
- If the first launch was itself opened by a link, that link wins and no deferred link is delivered on top of it.
- On a first launch without a link,
getInitialLink()waits for the launch report so it can return the deferred link. On later launches it returnsnullimmediately. - If the launch report fails (for example, offline at first launch), the SDK tries again in the same session: when the app comes back to the foreground, before
claimHandoff, and before sending events. A deferred link that comes back then arrives ondeepLinks. - The first launch marker is saved (with retries) before the link is handed over, so the same deferred link is not delivered again on the next launch.
Play Install Referrer
When someone without your app taps a Lander link on Android, Lander sends them to your Play Store listing with a short, single use token in the Play Store's install referrer. On first launch the SDK reads the Play Install Referrer once (waiting at most 5 seconds, and retrying once if the Play Store service is busy) and sends it to Lander, which turns it back into the link.
Your app receives it with isDeferred == true, confidence == LanderConfidence.DETERMINISTIC and method == "install_referrer". There is nothing to code: the SDK brings the com.android.installreferrer:installreferrer dependency and reads the referrer off the main thread.
It does not work when:
- The app was not installed from Google Play (another store, a sideloaded APK, a build from Android Studio, an emulator without Play).
- The install happened more than 7 days after the click (tokens expire) or the token was already used.
- The link has an Android fallback URL. That URL wins, and Lander cannot add a token to a page it does not control.
Authenticated handoff
If people sign in to your app, your own account system is a reliable bridge for the cases the referrer cannot cover. Typical case: someone taps an invite on their laptop, signs up on your website, then installs the app on their phone.
1. Your server binds a link to the user, with a secret key:
curl https://api.golander.link/v1/handoffs \
-H "Authorization: Bearer sk_live_..." \
-H 'Content-Type: application/json' \
-d '{"userId":"8f2c41d0-...","url":"https://acme.golander.link/invite-42"}'Instead of url you can send a linkId, or the lander_token of the exact click as token. A handoff expires after 7 days unless you pass a shorter expiresIn (in seconds).
2. The app claims it right after that user signs in:
lifecycleScope.launch {
val user = auth.signIn()
try {
val link = Lander.claimHandoff(user.id) // null when nothing is pending
if (link != null) navigate(link.route)
} catch (e: LanderException) {
// Offline, rate limited, or the install is not registered yet.
Log.w("Auth", "Handoff claim failed: ${e.code}")
}
}Lander.claimHandoff(user.getId(), new LanderCallback<LanderLink>() {
@Override public void onSuccess(LanderLink link) { if (link != null) navigate(link.getRoute()); }
@Override public void onError(Exception error) { Log.w("Auth", "Handoff claim failed", error); }
});suspend fun claimHandoff(userId: String): LanderLink?What to know:
- The link comes back with
isDeferred == true,confidence == DETERMINISTICandmethod == "authenticated_handoff". - It is returned only, never also emitted on
deepLinks, so navigate with it yourself. - The newest pending handoff wins, and claiming clears every pending handoff for that user. A second call returns
null. - If the handoff was made before the app was installed, the install is attributed to the link too.
- Retries reuse one
Idempotency-Key, so a lost response does not lose the link. userIdis trimmed and must be 1 to 256 characters, otherwiseclaimHandoffthrows anIllegalArgumentException. Lander stores only a salted hash of it.- It throws a
LanderExceptionwhen the request fails after retries, when the API rate limits it (10 claims per install per minute), or with codeinstall_not_registeredwhen the SDK could not report this install to Lander yet (for example, offline since the first launch). It tries that report again before giving up.
Use an ID nobody can guess
Pass your auth system's user UUID, never an email address or a counting number. Anyone who can guess the ID could claim the link.
Settings
Per project, shared by the test and live environments. Change them with PATCH /v1/deferred/settings and a secret key; changes apply to the next click. Only one matters for Android apps:
| Setting | Default | Effect |
|---|---|---|
webFallbackTokens | false | Redirects to your website carry ?lander_token=..., for handoffs bound to an exact click. |
The others (iosLandingPage, probabilisticMatching, probabilisticIpMatching, probabilisticWindowMinutes) only change what happens to iPhone clicks. See Deferred deep linking.
Unattributed installs
Every first launch that no flow could tie to a click is recorded as an unattributed install. Nothing is delivered and the app starts normally. That is not an error. If a deterministic link turns up later for that install (a handoff claim), the install is upgraded to it.