Deferred links
Links tapped before the app was installed, how each one reaches your app, and how much to trust it.
A deferred deep link is a link someone tapped before your app was installed. They go to the store, install, open the app, and should land on the screen the link was for instead of the home screen.
Lander does this wherever it can be done honestly, and labels every result with a confidence and a method. This page covers the app side. For the background (why iOS is harder, what is stored, privacy) read Deferred deep linking.
At a glance
| Platform | Flow | confidence | method | What you do |
|---|---|---|---|---|
| Android | Play Install Referrer | deterministic | install_referrer | Nothing. Works out of the box. |
| iOS | Continue in app page | deterministic | landing_page | Turn it on, set an App Store ID, add a custom scheme. |
| iOS and Android | Authenticated handoff | deterministic | authenticated_handoff | One call from your server, Lander.claimHandoff after sign in. |
| iOS | Probabilistic matching | probabilistic | probabilistic_match | Opt in, and treat the result as a hint. |
| Anything else | unattributed | Nothing. No link is delivered; the install is still counted. |
Handle them in your app
Deferred links arrive like every other link: on Lander.onDeepLink, or from Lander.getInitialLink(), exactly once. isDeferred is true. The one exception is claimHandoff, which returns its link to you directly.
Lander.onDeepLink.listen((link) {
if (link.isDeferred && link.confidence == LanderConfidence.probabilistic) {
// A good guess: show the screen, but do not act on it as if the user
// had tapped it in the app.
}
router.go(link.route, extra: link);
});Rules the SDK follows for you:
- Only a first launch can produce a deferred link (from the store, or after a reinstall, since the install ID does not survive an uninstall).
- 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.
Android: 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 and sends it to Lander, which turns it back into the link.
Your app receives it with isDeferred: true, confidence: deterministic and method: install_referrer. There is nothing to code.
It does not work when:
- The app was not installed from Google Play (another store, a sideloaded APK,
flutter run, 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.
iOS: Continue in app page
Apple gives apps no install referrer, so Lander offers a small page instead of an immediate App Store redirect. It shows your app and two buttons: Get the app on the App Store and I already have the app, open it. After installing, the person comes back to the page (it stays open in Safari) and taps the second button.
The app opens through that button, so the link arrives as an ordinary open on the stream, with confidence: deterministic, method: landing_page, and isDeferred: true when the app was installed after the click.
To use it:
- Set the App Store ID of your iOS app in Build > Platforms. The page needs it.
- Add a custom URL scheme to your app and enter it as the URI scheme of the iOS app. iOS keeps a Universal Link tapped on a page of its own domain inside Safari, so without a scheme the button may reload the page instead of opening your app. With a scheme the button opens
acmeshop://<path>?lander_link=<short link>, and the SDK resolves the short link inside it. - Turn on
iosLandingPagefor the project. There is no dashboard switch yet, so use the API with a secret key:
curl -X PATCH https://api.golander.link/v1/deferred/settings \
-H "Authorization: Bearer sk_live_..." \
-H 'Content-Type: application/json' \
-d '{"iosLandingPage": true}'Links with an iOS fallback URL skip the page and go to that URL.
Authenticated handoff
If people sign in to your app, your own account system is the most reliable bridge, on both platforms. 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:
final user = await auth.signIn();
try {
final link = await Lander.claimHandoff(user.id); // null when nothing is pending
if (link != null) router.go(link.route, extra: link);
} on LanderException catch (e) {
// Offline, rate limited, or the install is not registered yet.
debugPrint('Handoff claim failed: ${e.code}');
}static Future<LanderLink?> claimHandoff(String userId)What to know:
- The link comes back with
isDeferred: true,confidence: deterministicandmethod: authenticated_handoff. - It is returned only, never also emitted on
onDeepLink, 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 anArgumentError. 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).
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.
iOS: probabilistic matching
Off by default. When a project turns on probabilisticMatching, Lander keeps a few coarse facts about recent iPhone clicks (iOS major version, country, time). On an iOS first launch, if every recent click with the same iOS version and country points at the same link, the app gets it with confidence: probabilistic and method: probabilistic_match. Turning on probabilisticIpMatching as well matches clicks from the same network first, which finds far more installs.
The SDK sends nothing extra for this: only the OS version it always reports. Treat the result as a suggestion. Land on a product or content screen; never sign someone in, apply a referral credit or reveal anything private because of it.
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.
| Setting | Default | Effect |
|---|---|---|
iosLandingPage | false | iPhone clicks get the Continue in app page instead of an immediate App Store redirect. |
webFallbackTokens | false | Redirects to your website carry ?lander_token=..., for handoffs bound to an exact click. |
probabilisticMatching | false | Opt in iOS probabilistic matching. |
probabilisticIpMatching | false | With probabilistic matching on, also match by network (a keyed hash of the IP, kept at most 7 hours). |
probabilisticWindowMinutes | 120 | How far back probabilistic matching looks, 15 to 360. |
Check your privacy policy and App Store privacy details before turning on the probabilistic options. See Privacy.
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 (the Continue in app page, or a handoff claim), the install is upgraded to it.