Deferred Deep Links
Preserve a link's destination through a Play Store install, then route the user on first launch, with the WarpLink Android SDK.
Deferred deep links let you route users to specific content even when they don't have your app installed. The user clicks a link, installs from the Play Store, and on first launch the SDK matches them back to the original link.
How It Works
- User taps a WarpLink URL in Chrome
- WarpLink captures browser signals and sets the Play Install Referrer with
utm_source=warplink&utm_content={link_id} - User is redirected to the Play Store
- User installs and opens the app
- SDK detects first launch. A completion marker in the app's no-backup storage (
noBackupFilesDir) is written only after the check completes, and Auto Backup never restores it, so a reinstall attributes fresh - SDK reads the Play Install Referrer. If it contains
utm_source=warplink, a deterministic match is made (confidence 1.0) - If the referrer is unavailable, the SDK falls back to fingerprint matching (device language + timezone, with the IP derived server-side). Clicks that share a fingerprint are kept as a list, newest first, up to 10, so a second click never overwrites the first
- Server returns a
WarpLinkDeepLinkwithisDeferred = true
Automatic Check
The deferred check fires automatically from configure(). The result arrives in your onLink callback, where isDeferred is true:
WarpLink.configure(
context = this,
apiKey = "wl_live_yoursdkkeyhere000000000000000000",
options = WarpLinkOptions(onLink = { result ->
result.onSuccess { deepLink ->
if (deepLink.isDeferred) {
val confidence = deepLink.matchConfidence ?: 0.0
if (confidence > 0.5) {
navigateTo(deepLink.deepLinkUrl ?: deepLink.destination)
} else {
showWelcome(suggestedContent = deepLink.destination)
}
} else {
navigateTo(deepLink.destination) // a tap, not a deferred install
}
}
})
)
Manual Check (Advanced)
To run the check yourself, set automaticDeferredDeepLinks = false in WarpLinkOptions and call checkDeferredDeepLink:
WarpLink.checkDeferredDeepLink { result ->
result.onSuccess { deepLink ->
if (deepLink == null) {
showOnboarding()
return@onSuccess
}
val confidence = deepLink.matchConfidence ?: 0.0
if (confidence > 0.5) {
val target = deepLink.deepLinkUrl ?: deepLink.destination
navigateTo(target)
} else {
showWelcome(suggestedContent = deepLink.destination)
}
}.onFailure { error ->
Log.e("MyApp", "Deferred deep link error: ${error.message}")
showOnboarding()
}
}
Confidence Scores
Probabilistic scores depend on the fingerprint variant. enriched_tz hashes the IANA timezone name and is what an SDK that collects a zone name sends. enriched hashes the minute offset instead and stays as the fallback for older SDKs. basic drops the timezone entirely.
| Scenario | Confidence | Match Type |
|---|---|---|
| Play Install Referrer match | 1.0 | DETERMINISTIC |
enriched_tz fingerprint, < 1 hour | 0.85 | PROBABILISTIC |
enriched_tz fingerprint, < 3 hours | 0.65 | PROBABILISTIC |
enriched_tz fingerprint, < 6 hours | 0.50 | PROBABILISTIC |
enriched_tz fingerprint, < 24 hours | 0.30 | PROBABILISTIC |
The offset variant scores 0.80 / 0.60 / 0.45 / 0.25 across the same bands, and basic scores 0.70 / 0.50 / 0.35 / 0.20. Two further signals can only reduce the score: more than one distinct link in the fingerprint bucket applies x0.6, and a shared click IP applies x0.6 for carrier-grade NAT or a private address, x0.9 for a household IPv4 address, and x1.0 for IPv6. Past 24 hours there is no match at all.
Recommendation: Route to specific content when matchConfidence > 0.5. Show generic onboarding below 0.5. Gate anything sensitive on matchGuaranteed instead, which is true only for a deterministic match (the Play Install Referrer). A probabilistic match is a best guess made from a network-shaped fingerprint and can name the wrong user, so auto sign-in and personal data must never depend on a confidence threshold.
Match Window
The match window is set per link on the server, not in the SDK. Configure it in the dashboard when you create or edit a link. The default is 6 hours and the ceiling is 24 hours, so the 24 hour band above only applies to links configured past the default.
The window is deliberately short. The fingerprint key is a network (IP, language, timezone), not a device, so every extra hour lets another stranger behind the same shared address join the bucket while adding almost no real matches. Links created before the current limits may still carry a longer stored value, but the server caps every window at 24 hours when it reads them.
This governs probabilistic matching only. The Play Install Referrer branch is deterministic and is not affected by the window.
Caching Behavior
- The attribution check runs on first launch
- Once it completes, a completion marker in the app's no-backup storage (
noBackupFilesDir) is recorded so it does not run again for this install - Subsequent launches return without another network request
- An attempt that produced no usable answer (offline, or a match the SDK cannot route to a destination) is not recorded as complete, so it retries on the next launch
- A reinstall is a new install, so it gets a new check. See Reinstall Persistence
Edge Cases
Play Install Referrer Unavailable
The referrer is the primary matching method but is unavailable when:
- The app is sideloaded (
adb installor direct APK) - No Google Play Services (e.g., Huawei HMS devices)
- Referrer data has expired
The SDK automatically falls back to fingerprint matching. No code changes needed.
Offline First Launch
If the device has no connectivity on first launch, checkDeferredDeepLink fails with WarpLinkError.NetworkError. The attempt is not consumed, so the SDK retries the check on the next launch.
Reinstall Persistence
A reinstall is a new install and is attributed again. The SDK keeps two markers with two different jobs:
- Completion marker: a file in the app's no-backup storage (
noBackupFilesDir). It says the check already ran for this install. Auto Backup never captures that directory, so a reinstall starts from a clean marker - Device-seen marker: a
SharedPreferencesentry. It says the device was attributed at some point, and Auto Backup does restore it. It gates nothing. Its only job is to setis_reinstall: trueon the attribution request, so the install is recorded as a reinstall rather than a first install
Both installs count. The dashboard shows installs and reinstalls together as one installs number. No manifest or backup_rules.xml changes are required on your side: keep Auto Backup on and the two markers behave as described.
Multiple Links Before Install
Clicks that share a fingerprint are kept as a list, newest first, up to 10. The most recent click your app can claim is the one matched, and the others stay available for the other devices that share the address. When more than one click is claimable, the confidence score is multiplied by 0.6 to report that ambiguity.
Testing
- Uninstall the app from the test device
- Open the test link in Chrome. It redirects to the Play Store (or fallback URL)
- Install via Android Studio or
adb install - Launch the app.
checkDeferredDeepLinkshould return the matched deep link