Deferred Deep Linking in React Native: One API for iOS and Android
Deferred deep linking in React Native routes the pre-install link on first launch through one JavaScript API, backed by the Play referrer and iOS matching.
TL;DR: Deferred deep linking in React Native lets a user tap a link, install your app from the App Store or Play Store, and still land on the right screen on first launch. React Native's own
LinkingAPI cannot do this, becausegetInitialURLonly reports a URL the OS handed your app and a fresh install has no URL to hand over. The work happens in two halves instead: the click is recorded at the edge with the signals a browser exposes, and on first launch a native module collects the device's own signals so the server can match the install back to that click. The two platforms get there differently. Android reads the Play Install Referrer for a deterministic match, while a genuine first install on iOS falls back to a probabilistic fingerprint. Both surface through one JavaScript callback, wheredeepLink.isDeferredistrueandmatchGuaranteedtells you whether the match was deterministic or a guess. With@warplink/react-native1.1.0 that is oneWarpLink.configure({ apiKey, onLink })call and no per-platform branch. The rest of this guide is the mechanism, the TypeScript, and the testing.
Why React Native Has No Deferred Deep Linking API
React Native gives you exactly one door for incoming links, and it is Linking. You call Linking.getInitialURL() for the cold start case and Linking.addEventListener('url', handler) for the warm start case, and between them you cover every link that arrives while your app exists on the device.
That last clause is the whole problem. Both APIs are reporters: they tell you about a URL the operating system already routed into your process, and the OS only routes a URL into a process that is installed and registered for that domain. When the user taps your link without the app, there is no process, no registration, and no URL event. iOS sends them to the App Store, Android sends them to the Play Store, and the link's destination stops there. The user installs, opens the app, and getInitialURL() resolves to null, not because anything failed but because nothing was delivered.
Deferred deep linking is the workaround for a delivery channel that does not exist. Instead of transporting the link through the install, it records the click on one side, records the install on the other, and matches the two afterwards. This is also why no amount of React Navigation configuration fixes it: a linking config is a URL-to-screen mapping, and there is no URL. The deferred result arrives as a plain object from a native module, so it is your code, not the navigator, that routes it.
Two Platform Mechanisms Under One JavaScript API
Cross-platform deferred linking is interesting in React Native because iOS and Android solve the same problem with different machinery, and you want one code path on top of both.
Android: the Play Install Referrer
Android has a real answer. A redirect to the Play Store can attach a referrer string that the Play Store carries through the install and hands to the app afterwards. WarpLink sets that referrer to utm_source=warplink&utm_content={link_id}, so on first launch the native Android SDK reads it, sees the link id in plain text, and reports a deterministic match with confidence 1.0.
No inference and no window: the referrer names the click. This is why a test that works on Android tells you very little about how the same flow behaves on iPhone.
The referrer is not always there. It is missing when the app was sideloaded through adb install or a direct APK, when the device has no Google Play Services (Huawei devices running HMS, for example), and when the referrer data has expired. In each case the Android SDK falls back to the same fingerprint the iOS SDK uses, with no code change on your side.
iOS: IDFV first, then a fingerprint
iOS has no referrer at all. The App Store passes nothing through the install, so the strongest available signal is the Identifier for Vendor. The IDFV is stable across all apps from the same vendor on a device, it is always readable, and it is exempt from App Tracking Transparency. It produces a deterministic match with confidence 1.0 when the same install asks again.
Note that wording, because it is narrower than most write-ups suggest. A reinstall is deliberately not answered from the IDFV branch: it falls through to the probabilistic tier and is recorded there as a new install, which is what you want for install counting. So a genuine first install on iOS, the case you care about for a share link or a campaign, is matched probabilistically.
That tier compares signals captured when the link was tapped in the browser against signals collected when the app first launched: the IP address, the normalized primary language, and the timezone. The SDK sends the language and the timezone. It never sends the IP, because an app cannot see its own public address, so the server derives it from the request on both the click and the install side. Computing both halves in the same place is the only way the two hashes can agree.
The single JavaScript surface
Neither mechanism leaks into your code. Both platforms return the same WarpLinkDeepLink object through the same callback, and the only thing that differs by platform is the confidence in it:
interface WarpLinkDeepLink {
linkId: string;
destination: string;
deepLinkUrl: string | null;
customParams: Record<string, unknown>;
isDeferred: boolean;
matchType: 'deterministic' | 'probabilistic' | null;
matchConfidence: number | null;
matchGuaranteed: boolean;
}
isDeferred separates an install match from an ordinary tap. matchGuaranteed is what you branch on when the routing decision matters. Everything else is payload.
The Confidence Model and What to Trust
A deterministic match, whether it came from the Play Install Referrer or the IDFV, returns matchConfidence of 1.0, matchType of deterministic, and matchGuaranteed set to true. A probabilistic match returns a score that decays with the gap between click and install, and matchGuaranteed stays false.
The probabilistic ceilings depend on the fingerprint variant. Current SDKs send the IANA zone name, for example America/Toronto, which is the enriched_tz variant. Older SDKs send the minute offset instead, the enriched variant. When neither is usable, basic keys on IP and language alone.
| Time since click | enriched_tz | enriched | basic |
|---|---|---|---|
| < 1 hour | 0.85 | 0.80 | 0.70 |
| < 3 hours | 0.65 | 0.60 | 0.50 |
| < 6 hours | 0.50 | 0.45 | 0.35 |
| < 24 hours | 0.30 | 0.25 | 0.20 |
The zone name earns the higher ceiling because it carries far more entropy than the offset, roughly 340 zones against 38 offsets, and does not shift at a daylight saving boundary.
Two multipliers then reduce whichever ceiling applied, and they only ever reduce it, because a confident wrong answer is worse than an honest uncertain one. More than one claimable click sharing the fingerprint applies x0.6. The click's IP address applies x0.6 for carrier grade NAT or a private address, x0.9 for a household IPv4 address, and x1.0 for IPv6.
The match window governs this tier only. It is set per link on the server, in the dashboard, not in the SDK. The default is 6 hours and the ceiling is 24, so the bottom row of that table only applies to links configured past the default. The deterministic branches are unaffected by it.
The window is short deliberately. The fingerprint key describes a network, not a device, so every phone behind one shared address that shares a language and timezone lands in the same bucket, and each extra hour lets another stranger join it while adding almost no real matches. Inside the first hour, probabilistic matching is strong. By the end of the day it is a hint.
The practical rule that falls out of this: gate anything sensitive on matchGuaranteed, not on a confidence threshold. Auto sign in, restoring a session, showing personal data, resuming a purchase. A probabilistic match can name the wrong person, and no threshold tells you when it did. Confidence is the second decision, and it only applies once matchGuaranteed is false: route to specific public content above 0.5, soften below it.
Wiring It Up: One Call, Both Platforms
The package is @warplink/react-native 1.1.0, MIT licensed with no third party runtime dependencies. It needs React Native 0.75 or newer, React 18 or newer, iOS 15 or newer, and Android API 26 or newer.
npm install @warplink/react-native@1.1.0
cd ios && pod install
The iOS side comes through Swift Package Manager and is pulled in by the podspec, which requires dynamic frameworks. Add this inside your app's target block in ios/Podfile first:
use_frameworks! :linkage => :dynamic
Android auto links through the React Native CLI with no extra step.
Then call configure() once at startup, outside any component, so it runs before the first render rather than inside a useEffect.
// src/warplink.ts
import { WarpLink, type WarpLinkDeepLink } from '@warplink/react-native';
function handleLink(deepLink: WarpLinkDeepLink): void {
if (deepLink.isDeferred) {
// Matched from an install, not from a tap. Route by confidence.
routeDeferred(deepLink);
return;
}
routeTap(deepLink.deepLinkUrl ?? deepLink.destination);
}
WarpLink.configure({
apiKey: 'wl_live_yoursdkkeyhere000000000000000000',
onLink: ({ deepLink, error }) => {
if (error) {
console.warn('WarpLink error:', error.code, error.message);
return;
}
if (deepLink) {
handleLink(deepLink);
}
},
}).catch((configureError) => {
console.warn('WarpLink configure failed:', configureError);
});
That single call wires three sources into one callback: cold start (launched by a link), warm start (foregrounded by a link), and the deferred check. deepLink.isDeferred tells the third apart from the first two.
Two behaviors of configure() are worth knowing before you debug anything. It does not throw on a malformed key: it validates the format synchronously, reports a bad one through onLink as an { error } event with code E_INVALID_API_KEY_FORMAT, logs a warning, and leaves the SDK unconfigured. And an exception thrown by your own onLink propagates out of the promise configure() returns rather than coming back to you as a fabricated SDK error. That is why the .catch() is on the example above: on the first launch after an install, a navigation call inside onLink is the most likely thing in your app to throw.
Either automatic piece can be disabled and driven yourself:
WarpLink.configure({
apiKey: 'wl_live_yoursdkkeyhere000000000000000000',
automaticDeepLinks: false, // wire onDeepLink / getInitialDeepLink yourself
automaticDeferredDeepLinks: false, // call checkDeferredDeepLink yourself
});
Note the asymmetry. Omitting onLink does not switch the deferred check off, because that request is what attributes the install. Only automaticDeferredDeepLinks: false stops it.
Native host setup, and what deferred matching does not need
Two native hooks are required for tapped links, and neither is required for deferred matching. Separate them so you debug the right layer.
On iOS, your AppDelegate must forward incoming URLs to the SDK, because iOS does not hand Universal Links to native modules on its own:
// ios/YourApp/AppDelegate.swift
import React
import warplink_react_native
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
WarpLinkModule.handleIncomingURL(url)
}
return RCTLinkingManager.application(
application, continue: userActivity, restorationHandler: restorationHandler)
}
handleIncomingURL returns nothing and does no domain filtering, so never let it decide the delegate's return value. Keep returning whatever your existing linking code returned, or the deep links your app already handles stop arriving.
On Android, set android:launchMode="singleTask" on your launch Activity so warm start intents reach the SDK through onNewIntent. Cold start is read from the launch intent automatically.
Neither touches the deferred path, which runs from configure() straight to the attribution endpoint. A missing AppDelegate call breaks tapped Universal Links while deferred matching keeps working, and a missing launchMode breaks warm start the same way. If your deferred link is fine but a tapped one is not, the native host setup is where to look.
If your links are served from your own domain rather than aplnk.to, declare it so the SDK recognizes it on the first launch, before it has fetched your domain list:
WarpLink.configure({
apiKey: 'wl_live_yoursdkkeyhere000000000000000000',
linkDomains: ['links.yourapp.com'],
onLink: ({ deepLink }) => deepLink && handleLink(deepLink),
});
The Manual Check and the Splash Gate
The automatic dispatch is right for most apps, with one exception: when your first screen depends on the answer and you would rather hold a splash than render onboarding and then yank it away. Disable the automatic dispatch and await the check yourself. checkDeferredDeepLink() returns Promise<WarpLinkDeepLink | null>, resolving to null when there was no match.
// App.tsx
import { useEffect, useState } from 'react';
import { WarpLink, type WarpLinkDeepLink } from '@warplink/react-native';
import { RootNavigator } from './src/RootNavigator';
import { SplashScreen } from './src/SplashScreen';
WarpLink.configure({
apiKey: 'wl_live_yoursdkkeyhere000000000000000000',
automaticDeferredDeepLinks: false,
});
export default function App(): React.JSX.Element {
const [isReady, setIsReady] = useState(false);
const [deferredLink, setDeferredLink] = useState<WarpLinkDeepLink | null>(null);
useEffect(() => {
let cancelled = false;
WarpLink.checkDeferredDeepLink()
.then((link) => {
if (!cancelled && link?.isDeferred) {
setDeferredLink(link);
}
})
.catch((error: Error) => {
// Offline on first launch resolves as E_NETWORK_ERROR. The attempt is
// not consumed, so the SDK retries on the next launch.
console.warn('Deferred check failed:', error.message);
})
.finally(() => {
if (!cancelled) {
setIsReady(true);
}
});
return () => {
cancelled = true;
};
}, []);
if (!isReady) {
return <SplashScreen />;
}
return <RootNavigator initialDeferredLink={deferredLink} />;
}
Two properties of the check make this safe. It runs once per install: the native SDK writes a completion marker only after the check has definitively completed, and every later call resolves from the cache with no network request. And an attempt that produced no usable answer, such as an offline first launch, is not recorded as complete, so it retries on the next launch instead of caching a permanent null.
That marker is install scoped on purpose. It is a backup excluded file in the app container on iOS and a file in noBackupFilesDir on Android, and no restore brings either back, so a reinstall attributes fresh. A separate device level marker outlives the uninstall (the Keychain on iOS, SharedPreferences on Android) and only sets is_reinstall on the request.
Routing With React Navigation 7
Here is the part that catches people. React Navigation 7 configures URL based deep links with the linking prop:
import { NavigationContainer } from '@react-navigation/native';
const linking = {
prefixes: ['https://aplnk.to', 'myapp://'],
config: {
screens: {
Product: 'product/:id',
Home: '',
},
},
};
That config will never route a deferred link. It maps URLs to screens, and a deferred match is not a URL: it is an object arriving from a native module after the app has already launched. Keep linking for tapped links, and route the deferred object yourself, through a navigation ref, because the callback can fire before the navigator has mounted. On the first launch after an install, it usually does.
// src/navigation.ts
import { createNavigationContainerRef } from '@react-navigation/native';
import type { WarpLinkDeepLink } from '@warplink/react-native';
export type RootStackParamList = {
Home: undefined;
Product: { id: string };
Welcome: { suggestion: string | null };
};
export const navigationRef = createNavigationContainerRef<RootStackParamList>();
let pendingLink: WarpLinkDeepLink | null = null;
export function routeDeferred(link: WarpLinkDeepLink): void {
if (!navigationRef.isReady()) {
// The navigator has not mounted yet. Hold the link and let onReady flush it.
pendingLink = link;
return;
}
const productId =
typeof link.customParams['product_id'] === 'string'
? (link.customParams['product_id'] as string)
: null;
// Deterministic only. Identity work needs a guarantee, not a high score.
if (link.matchGuaranteed && productId) {
navigationRef.navigate('Product', { id: productId });
return;
}
const confidence = link.matchConfidence ?? 0;
if (confidence > 0.5 && productId) {
navigationRef.navigate('Product', { id: productId });
} else if (confidence > 0.3) {
navigationRef.navigate('Welcome', { suggestion: link.destination });
} else {
navigationRef.navigate('Welcome', { suggestion: null });
}
}
export function flushPendingLink(): void {
if (pendingLink && navigationRef.isReady()) {
const link = pendingLink;
pendingLink = null;
routeDeferred(link);
}
}
Then attach the ref and flush from onReady, which React Navigation calls once the container has mounted and the ref is usable:
// src/RootNavigator.tsx
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { navigationRef, flushPendingLink, type RootStackParamList } from './navigation';
import { HomeScreen } from './HomeScreen';
import { ProductScreen } from './ProductScreen';
import { WelcomeScreen } from './WelcomeScreen';
const Stack = createNativeStackNavigator<RootStackParamList>();
export function RootNavigator(): React.JSX.Element {
return (
<NavigationContainer ref={navigationRef} onReady={flushPendingLink}>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Product" component={ProductScreen} />
<Stack.Screen name="Welcome" component={WelcomeScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
On React Navigation 7's static API the same two props exist on the component createStaticNavigation returns, so ref and onReady move there unchanged.
The typed RootStackParamList is worth the extra lines. customParams is a Record<string, unknown> by design, because the values come from whatever pairs were attached to the link, so narrow each one before navigating with it.
Expo: What Works and What You Have to Add
Expo works, with one caveat and one gap, and both are worth being precise about.
The caveat: deferred matching needs a native module, and Expo Go cannot load native modules. A development build is required:
npx expo install @warplink/react-native
npx expo prebuild
npx expo run:ios
Use that development build, or an EAS build, for every test. Expo Go reports the module as unlinked, which looks like a broken SDK and is not one.
The associated domains entitlement and the Android intent filter both go in app.json and survive prebuild:
{
"expo": {
"ios": {
"associatedDomains": ["applinks:aplnk.to"]
},
"android": {
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [{ "scheme": "https", "host": "aplnk.to" }],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}
The gap: there is no WarpLink config plugin. expo prebuild regenerates a stock AppDelegate with no WarpLink call in it, so a call you added by hand is gone after the next prebuild. Two supported ways around that, from the SDK's own integration guide:
- Commit the generated
ios/directory and add thehandleIncomingURLcalls to theAppDelegatethere. - Add the calls from your own Expo config plugin using
withAppDelegate, so they survive every prebuild.
The Android launch mode has the same shape of answer: set it through the expo-build-properties config plugin or under android in app.json.
Worth repeating, since it changes how much of this blocks you: the AppDelegate call is required for tapped Universal Links, not for the deferred check. An Expo app that has not solved that question yet still attributes installs and still receives deferred matches. It just will not route a tap from someone who already has the app.
For a custom domain, set the iOS WarpLinkDomains key through expo.ios.infoPlist in app.json and the Android app.warplink.DOMAINS entry from a config plugin, or skip both and pass linkDomains to configure().
Testing Deferred Deep Links on Each Platform
Deferred matching is the hardest thing in a linking stack to test, because every shortcut you would normally take invalidates the test. The rule underneath all of it: a deferred check fires once per install, on the genuine first launch.
Both platforms, first. Create a test link in the dashboard with a destination and, if you have one, an iOS and Android deep link URL. Turn on debugLogging: true in configure() so the native side traces what it did. Keep the gap between tapping the link and opening the installed app short, ideally inside an hour, both to stay inside the match window and to land in the top confidence band.
iOS. Use TestFlight. Re-running a build from Xcode is not a fresh install and does not reset the completion marker, so it returns the cached result and looks exactly like a broken match. Deleting the app and installing it again is enough for an ordinary retest. For the first install path rather than the reinstall path, erase the simulator (Device, then Erase All Content and Settings, or xcrun simctl erase) or use a device that has never run the app.
Android. Use an internal testing track install. That is the only route that produces a real Play Install Referrer, so it is the only way to test the deterministic path Android users actually get. A sideloaded APK has no referrer and falls through to the fingerprint. That is still a useful test, since it exercises the path iOS uses, but do not read it as proof the referrer branch works.
What a good result looks like. On Android through the Play track: matchType of deterministic, matchConfidence of 1.0, matchGuaranteed true. On iOS through TestFlight on a device that never had the app: matchType of probabilistic, matchGuaranteed false, and around 0.85 if you tested inside the hour. Get those two and the wiring is correct.
Why Isn't It Firing?
Almost every report of "deferred deep linking does not work in React Native" is one of these, and most are testing artifacts rather than bugs.
- The navigator was not ready. The deferred dispatch runs from
configure(), which you called at module scope, before any component mounted. Anavigate()inonLinkthrows, and because an exception from your callback propagates out of theconfigure()promise, it surfaces as an unhandled rejection instead of a navigation. Queue the link, flush it fromonReady, and keep the.catch(). - You used an API key instead of an SDK key. They share the
wl_live_prefix and both pass the format check, but an API key cannot record installs. Deep links keep resolving, so the integration looks healthy while every attribution call is rejected. Create the credential under API Keys, SDK key. - You tested by re-running from Xcode or reloading the bundle. Neither is a fresh install. The completion marker survives both, so the check returns the cached result with no network request.
- The match window expired. Once the click has aged out,
checkDeferredDeepLink()resolves tonullwith nothing wrong anywhere. The default is 6 hours, set per link in the dashboard, with a 24 hour ceiling. - You are in Expo Go. The native module cannot load, and the SDK throws a linking error saying exactly that. Use a development build.
- You expected
Linking.getInitialURL()to see it. It never will. A deferred match does not travel as a URL, so it appears in neither React Native'sLinkingAPI nor React Navigation'slinkingconfig. - Offline on first launch. The check rejects with
E_NETWORK_ERRORand the attempt is deliberately not consumed, so it retries on the next launch. Not a state to code around, but an offline first launch does show your default onboarding. - You got the same match twice. That is the cache working. The manual
checkDeferredDeepLink()always returns the cached result, while the automatic dispatch throughonLinkdelivers a match at most once across launches. Route from one or the other.
For historical context, this class of problem is why so many React Native teams reached for Firebase Dynamic Links before it shut down in August 2025. The mechanism above is what replaced it.
If the link fires but the app opens a browser instead of your screen, that is Universal Link or App Link routing rather than deferred matching, and it is diagnosed against the association files.
Frequently Asked Questions
Does deferred deep linking work with Expo?
Yes, in a development build. Deferred matching needs a native module, so it cannot run in Expo Go. Install the package with npx expo install, run npx expo prebuild, and build with EAS or npx expo run:ios. There is no WarpLink config plugin today, so the iOS AppDelegate call has to be added through your own withAppDelegate plugin or in a committed ios/ directory.
Why does nothing happen on the first launch after install?
The three usual causes are a navigator that was not ready when the callback fired, an API key used where an SDK key was required, and a store gap longer than the match window. Queue the link and flush it from the navigation container's onReady, check that the key came from API Keys then SDK key in the dashboard, and keep the click to install gap short while testing.
Why is Android deferred matching more accurate than iOS?
The Play Store passes the click referrer through the install, so Android gets a deterministic match with confidence 1.0 and matchGuaranteed true. The App Store passes nothing equivalent, so a genuine first install on iOS is matched by a probabilistic fingerprint that decays from 0.85 inside the first hour. The same JavaScript handles both, but the confidence you get back differs by platform.
How do I test deferred deep links without publishing to the stores? On iOS, use TestFlight: an Xcode rerun is not a fresh install and returns the cached result. On Android, use an internal testing track install, because that is the only way to exercise the real Play Install Referrer. A sideloaded APK still tests the fingerprint fallback, which is the path iOS uses anyway.
Do I need to call checkDeferredDeepLink myself in React Native?
No. configure() fires the check automatically and delivers a match through onLink with isDeferred set to true. Call checkDeferredDeepLink() yourself only when you set automaticDeferredDeepLinks: false, or when you want to gate your first screen on the result behind a splash. Note that omitting onLink does not switch the check off, because that request is what attributes the install.
Does deferred deep linking need the IDFA or an App Tracking Transparency prompt? No. The iOS side uses the IDFV, which is vendor scoped and exempt from App Tracking Transparency, plus a fingerprint the server computes from the request. The SDK never touches the IDFA or the Google Advertising ID, so it adds no permission dialog to your first launch on either platform.
Related Guides
- Each platform in depth: Deferred Deep Linking on iOS and Deferred Deep Linking on Android go further into the IDFV, Private Relay, and the Play Install Referrer than a cross platform post can.
- The setup underneath this one: React Native Deep Linking covers the entitlement, intent filter, and React Navigation wiring for tapped links.
- Start here for the concepts: The Complete Deep Linking Guide for Mobile Developers.
- When a tapped link misbehaves: Universal Links Not Opening? for iOS, and Android App Links autoVerify Failed for the Android association file.
- Reference: the React Native deferred deep links docs, the SDK setup guide, the attribution docs, and the deferred deep links concept page.
How WarpLink Helps
Everything above is the mechanism, and the mechanism does not change depending on who runs it. What a service has to provide are the two halves you cannot host inside your app: the click recorder at the edge that captures signals before the store takes over, and the attribution endpoint that computes the fingerprint from the request IP and walks the match cascade. WarpLink's React Native SDK is the bridge between those and your JavaScript, and it collapses to WarpLink.configure({ apiKey, onLink }), one callback, and an isDeferred check. It is MIT licensed with no third party runtime dependencies, so the bridge is a few files you can read line by line.
The part worth saying plainly is that deferred deep linking is install attribution. The same match that routes the user also tells you which link, which campaign, and which share drove the install, because matchType, matchConfidence, matchGuaranteed, and customParams come back in the same payload. Route the user and attribute the install in one call. That is the bridge from the linking pillar to the attribution pillar, and from there to the real time analytics that show which taps became users, on iOS and Android, from one codebase.
Create a free WarpLink account and get deep linking, install attribution, and analytics in one SDK, with 10,000 clicks a month on the free tier and no time limit. The React Native SDK docs have the full setup.
WarpLink Team
Building affordable, reliable link infrastructure for mobile teams. Deep linking, install attribution, and real-time analytics in one SDK.
Related Posts
Deferred Deep Linking on Android: How to Implement It in Kotlin
Deferred deep linking on Android hands the tapped link to the app on first launch through the Play Install Referrer, with fingerprint matching as the fallback.
QR Code Deep Linking: Route Scans Into Your App, Even Before Install
A QR code deep link is an HTTPS URL that a scan opens with a real user tap, so it must point at a redirect you control. How to route and attribute every scan.
Deferred Deep Linking on iOS: How to Implement It in Swift
A vendor-neutral Swift walkthrough of deferred deep linking on iOS: why iOS has no native support, the match cascade (IDFV, fingerprint, raw signals), the ATT and Private Relay reality, and a one-call first-launch implementation.