React Native Deferred Deep Links
Preserve a link's destination through App Store and Play Store installs, then route the user on first launch, in React Native.
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 or Play Store, and on first launch the SDK matches them back to the original link.
How It Works
- User taps a WarpLink URL in a browser
- WarpLink records the click. The server derives the IP and stores it with the normalized language and timezone. Clicks that share a match key 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 (iOS) or Play Store (Android)
- User installs and opens the app
- SDK detects first launch. A completion marker is written only after the check completes. It is scoped to one install on both platforms, and no backup restores it, so a reinstall attributes fresh. On iOS, if the backup-excluded file cannot be written, the SDK falls back to a UserDefaults flag that a restore does carry; that rare case is the one exception
- SDK collects device signals (preferred language, timezone name and offset, and platform IDs) and sends them to the attribution API. The server derives the IP and computes the match key
- Server matches against stored click data
- Deep link returned with
isDeferred: true
Automatic Check
The deferred check fires automatically from configure(). The result arrives in your onLink callback, where isDeferred is true:
import { WarpLink } from '@warplink/react-native';
WarpLink.configure({
apiKey: 'wl_live_yoursdkkeyhere000000000000000000',
onLink: ({ deepLink, error }) => {
if (error || !deepLink) return;
if (deepLink.isDeferred) {
const confidence = deepLink.matchConfidence ?? 0;
if (confidence > 0.5) navigateTo(deepLink.deepLinkUrl ?? deepLink.destination);
} else {
navigateTo(deepLink.destination); // a tap, not a deferred install
}
},
});
If that check cannot reach the server, for example on a launch with no network, the failure also arrives in onLink. The first-launch gate stays open, so the next launch retries by itself. Treat an E_NETWORK_ERROR there as a report, not as something to act on.
Manual Check (Advanced)
To run the check yourself, set automaticDeferredDeepLinks: false and call checkDeferredDeepLink():
import { useEffect, useState } from 'react';
import { WarpLink, type WarpLinkDeepLink } from '@warplink/react-native';
function App() {
const [isReady, setIsReady] = useState(false);
useEffect(() => {
WarpLink.checkDeferredDeepLink()
.then((link) => {
if (link?.isDeferred) {
navigateTo(link.deepLinkUrl ?? link.destination);
}
})
.catch((error) => {
console.warn('Deferred deep link check failed:', error.message);
})
.finally(() => {
setIsReady(true);
});
}, []);
if (!isReady) return <SplashScreen />;
return <>{/* Your app */}</>;
}
Confidence-Based Routing
const link = await WarpLink.checkDeferredDeepLink();
if (!link?.isDeferred) return;
const confidence = link.matchConfidence ?? 0;
if (confidence > 0.5) {
// High confidence: navigate straight to the link's destination
navigation.navigate('WebView', { url: link.deepLinkUrl ?? link.destination });
} else if (confidence > 0.3) {
// Medium confidence: show suggestion
navigation.navigate('Suggestion', {
message: 'Were you looking for this?',
url: link.destination,
});
}
// Below 0.3: ignore, show default onboarding
Confidence Scores
Probabilistic scores depend on the match key 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 repeat check, same install asking again (iOS, not a reinstall) | The stored install's own score | The stored install's own type |
| Play Install Referrer (Android) | 1.0 | deterministic |
enriched_tz match key, < 1 hour | 0.85 | probabilistic |
enriched_tz match key, < 3 hours | 0.65 | probabilistic |
enriched_tz match key, < 6 hours | 0.50 | probabilistic |
enriched_tz match key, < 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 adjust the score: more than one distinct link in the match key 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.
A Play Install Referrer match sets matchGuaranteed to true on the returned link. Use the flag, like matchConfidence, to pick a destination, and keep sign-in on your own authentication.
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 maximum is 24 hours.
The window is deliberately short so the match key bucket stays tight and matches stay precise. 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 not affected by the window, and neither is an IDFV repeat check, which returns an install the device already has.
Platform Differences
| iOS | Android | |
|---|---|---|
| Deterministic match | None (the IDFV only recognizes the same install asking again) | Play Install Referrer |
| Completion marker | Backup-excluded file in the app container | File in noBackupFilesDir |
| Device-seen marker | Keychain | SharedPreferences (restored by Auto Backup) |
| ATT required? | No (IDFV is exempt) | N/A |
The completion marker is per install and no backup restores it, so a reinstall re-attributes on both platforms. The device-seen marker outlives an uninstall on both platforms and only sets is_reinstall on the request. The one exception is the iOS fallback described above.
Caching Behavior
- Attribution check happens once per install (first launch only)
- Result cached by the native SDK
- Subsequent calls to
checkDeferredDeepLink()resolve with the cached result, the matched deep link if there was one andnullif there was not, without another network request - A reinstall is a new install, so it gets a new check. See App Reinstall
Edge Cases
Offline First Launch
checkDeferredDeepLink() rejects with E_NETWORK_ERROR. The attempt is not consumed, so the SDK retries the check on the next launch.
Check for connectivity before calling checkDeferredDeepLink() if your first-launch experience depends on it.
App Reinstall
A reinstall is a new install and is attributed again on both platforms. The JavaScript layer does nothing special here: it bridges to the native SDK, which keeps two markers with two different jobs.
- Completion marker. Says the check already ran for this install, and it is gone once the app is. iOS keeps it in a backup-excluded file in the app container, Android in
noBackupFilesDir. Neither comes back from a restore, so a reinstall starts clean. The one exception is the iOS fallback described above. - Device-seen marker. Says the device was attributed at some point, and it outlives an uninstall. iOS keeps it in the Keychain, Android in
SharedPreferences, which Auto Backup restores. It gates nothing. Its only job is to setis_reinstall: trueon the attribution request
Both installs count. The dashboard shows installs and reinstalls together as one installs number.
Routing-only mode
If the app is in routing-only mode, the server skips the probabilistic match. On Android an install that carries a Play Install Referrer still matches. No SDK change is needed.
Multiple Links Before Install
Clicks that share a match key 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
Deleting or uninstalling the app is enough to retest. The completion marker goes with it, so the next install runs the check again:
- Delete (iOS) or uninstall (Android) the app from the test device
- Open the test link in Safari or Chrome. You're redirected to the App Store, Play Store, or fallback URL
- Install the app via Xcode/TestFlight or Android Studio/
adb install - Launch the app.
checkDeferredDeepLink()should resolve with the matched deep link
The request from step 4 carries is_reinstall: true, because the device-seen marker from the earlier install is still on the device. To test the first-install path instead, use a device or simulator/emulator that has never run the app, or erase it first: on iOS Simulator, Device > Erase All Content and Settings (or xcrun simctl erase); on Android, clear app data along with an uninstall, since SharedPreferences on Android is restored by Auto Backup.
React Native Deep Links
Handle deep links in your React Native app across iOS and Android, covering both cold start and warm start with the WarpLink SDK.
React Native Install Attribution
How the WarpLink React Native SDK attributes installs across iOS and Android, with match types, confidence scores, and privacy controls.