Deferred Deep Links
Preserve a link's destination through an App Store install, then route the user on first launch, with the WarpLink iOS 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 App Store, and on first launch the SDK matches them back to the original link.
How It Works
- User taps a WarpLink URL in Safari
- WarpLink's redirect page records the click. The server derives the IP and stores it with the normalized language and timezone. Clicks that share a fingerprint are kept as a list, newest first, up to 10, so a second click never overwrites the first
- User is redirected to the App Store
- User installs and opens the app
- SDK detects first launch. The completion marker is written only after the check completes, so a failed attempt retries on the next launch. It lives in a backup-excluded file inside the app container, so it goes away with the app and does not come back from an iCloud or iTunes restore
- SDK collects device signals (preferred language, timezone name and offset, IDFV)
- SDK sends signals to the attribution API. The server derives the IP from the request and computes the fingerprint
- Server matches against stored click data and returns the deep link
Automatic Check
The deferred check fires automatically from configure(). The result arrives in your onLink callback, where isDeferred is true:
WarpLink.configure(
apiKey: "wl_live_yoursdkkeyhere000000000000000000",
options: WarpLinkOptions(onLink: { result in
guard case .success(let deepLink) = result, let deepLink else { return }
if deepLink.isDeferred {
let confidence = deepLink.matchConfidence ?? 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 autoDeferredCheck: false in WarpLinkOptions and call checkDeferredDeepLink early in your first-launch flow:
WarpLink.checkDeferredDeepLink { result in
switch result {
case .success(let deepLink):
guard let deepLink = deepLink else {
// No deferred deep link — show default onboarding
showOnboarding()
return
}
// Route based on confidence
let confidence = deepLink.matchConfidence ?? 0
if confidence > 0.5 {
// High confidence — route to specific content
if let deepLinkUrl = deepLink.deepLinkUrl {
navigateTo(deepLinkUrl)
} else {
navigateTo(deepLink.destination)
}
} else {
// Low confidence — show generic welcome with a hint
showWelcome(suggestedContent: deepLink.destination)
}
case .failure(let error):
// Network error on first launch — show default experience
print("Deferred deep link error: \(error.localizedDescription)")
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 |
|---|---|---|
| IDFV re-engagement (previously installed) | 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 (IDFV). 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 controls how far back the server looks for matching clicks. It 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 IDFV branch is deterministic and is not affected by the window.
Caching Behavior
- The SDK checks for a deferred deep link only on first launch
- Once the check completes, a completion marker 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 App Reinstall
Edge Cases
Offline First Launch
If the device has no connectivity on first launch, checkDeferredDeepLink fails with .networkError. The attempt is not consumed, so the SDK retries the check on the next launch.
If connectivity is critical for first-launch, check for network availability before calling checkDeferredDeepLink.
App Reinstall
A reinstall is a new install and is attributed again. The SDK keeps two markers with two different jobs:
- Completion marker: a backup-excluded file in the app container. It says the check already ran for this install. Deleting the app deletes it, and an iCloud or iTunes restore does not bring it back, so a reinstall starts clean
- Device-seen marker: a Keychain item. It says the device was attributed at some point, and it outlives an app delete. It gates nothing. Its only job is to set
is_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.
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 Deferred Deep Links
Deleting the app is enough to retest. The completion marker goes with it, so the next install runs the check again:
- Delete the app from the test device
- Open the test link in Safari. You'll be redirected to the App Store (or fallback URL)
- Install the app via Xcode or TestFlight
- Launch the app.
checkDeferredDeepLinkshould return the matched deep link
The request from step 4 carries is_reinstall: true, because the Keychain marker from the earlier install is still on the device. To test the first-install path instead, erase the device: on a simulator use Device > Erase All Content and Settings (or xcrun simctl erase), which clears the Keychain along with everything else, and on a physical device use one that has never run the app.