Deferred Deep Linking After Firebase Dynamic Links: App Clips + Play Install Referrer

A user taps your link — a shared product, an invite, a password reset — and the app is not installed yet. They land in the App Store or Google Play, install the app, open it, and… they are dropped on a generic home screen. The context of what they tapped is gone. That broken moment is exactly what deferred deep linking is meant to fix, and for years most teams solved it with Firebase Dynamic Links. That option no longer exists.
This article explains how we built a deferred deep linking system that survives an install — and can optionally wait for a business event like login before it routes the user — using iOS App Clips and the Google Play Install Referrer API instead of a third-party service.
Key takeaways
- Deferred deep linking preserves a link's destination across an app install, so a first launch lands on the right screen instead of the home screen.
- Firebase Dynamic Links was deprecated and shut down on 25 August 2025 — the most common solution is now gone, and existing links no longer resolve.
- The popular do-it-yourself fallbacks — device fingerprinting and clipboard matching — are unreliable, privacy-fragile, and discouraged by Apple.
- On iOS, a lightweight App Clip captures the link before the full app exists and hands it off through a shared App Group container after install.
- On Android, the official Play Install Referrer API delivers the link payload deterministically on first launch.
- A pending-link queue lets you postpone navigation until both the install completes and an optional business event (such as a successful login) has fired.
What is deferred deep linking?
A deep link opens a specific screen inside an app — for example myapp://product/42. A deep link only works if the app is already installed. Deferred deep linking removes that precondition: when the app is not installed, the destination is remembered through the store visit and the install, and the app navigates to it on first launch.
The hard part is the gap. Between the tap and the first launch, the operating system installs a fresh app that has no memory of what the user tapped. Carrying a payload across that gap — reliably and without invading privacy — is the entire problem.
The standard solution is gone: Firebase Dynamic Links
For most of the last decade, the default answer was Firebase Dynamic Links. It handled the store redirect, the deferred payload, and the first-launch resolution for both platforms behind one SDK.
Google deprecated Dynamic Links and fully shut the service down on 25 August 2025. Links stopped resolving, and there is no drop-in replacement from Google. Paid attribution platforms such as Branch and AppsFlyer fill the gap commercially, but many teams want to own the flow without a per-event vendor bill or sending user clicks to a third party.
Firebase Dynamic Links is no longer an option
If your deferred deep linking still depends on Firebase Dynamic Links, it stopped working on 25 August 2025. Migrating to Universal Links and Android App Links handles the installed-app case — but not the deferred (not-yet-installed) case. That gap is what this approach closes.
The common DIY fallbacks — and their pitfalls
Once Dynamic Links is off the table, most guides reach for one of two homegrown techniques. Both are worth understanding precisely because they are fragile.
Device fingerprinting. The web page records a "fingerprint" (IP address, screen size, OS version, locale) at tap time. On first launch, the app sends its own fingerprint to a server that tries to match the two within a short window. The pitfalls are serious: shared/carrier IPs cause mismatches, the match window is short, accuracy drops on busy networks, and probabilistic matching of users is exactly the pattern Apple's privacy rules push against.
Clipboard / pasteboard. The web page copies the payload to the clipboard; the app reads it on launch. Since iOS 14 this triggers a visible "pasted from Safari" banner, users can wipe the clipboard, and any other copy in between destroys the payload.
| Approach | Platform | Reliability | Privacy | Status |
|---|---|---|---|---|
| Firebase Dynamic Links | iOS + Android | High | Acceptable | Shut down Aug 2025 |
| Device fingerprinting | iOS + Android | Low–medium | Poor | Discouraged |
| Clipboard matching | iOS | Medium | Paste banner shown | Fragile |
| App Clip + App Group (ours) | iOS | High | Good | Recommended |
| Play Install Referrer API | Android | High | Good | Official |
Our approach: App Clips on iOS, Install Referrer on Android
Instead of guessing who the user is after install, we capture the link deterministically on each platform using a first-party mechanism, then converge on a single resolution step inside the app.
iOS: capture the link with an App Clip
An App Clip is a tiny slice of your app (under 15 MB) that launches almost instantly from a link, an App Clip Code, or a QR code — without a full install. That property is exactly what we need: the App Clip runs before the full app exists, so it can see the original link.
The flow:
- The link opens the App Clip. iOS delivers the invocation URL through an
NSUserActivity. - The App Clip writes that URL (plus a timestamp) into a shared App Group container that the full app can also read.
- The App Clip shows an App Store overlay prompting the user to install the full app.
- After install, the full app reads the pending link from the same App Group container on first launch.
// App Clip — capture the invocation URL into the shared App Group
func scene(_ scene: UIScene, continue activity: NSUserActivity) {
guard activity.activityType == NSUserActivityTypeBrowsingWeb,
let url = activity.webpageURL else { return }
let shared = UserDefaults(suiteName: "group.pro.nerdy.deeplink")
shared?.set(url.absoluteString, forKey: "pendingDeepLink")
shared?.set(Date(), forKey: "pendingDeepLinkAt")
}
// Full app — read it once, on first launch after install
let shared = UserDefaults(suiteName: "group.pro.nerdy.deeplink")
if let link = shared?.string(forKey: "pendingDeepLink") {
DeepLinkQueue.shared.enqueue(link)
shared?.removeObject(forKey: "pendingDeepLink")
}
Because the App Clip and the full app share the same App Group, the payload is handed off exactly — no fingerprint matching, no clipboard prompt, no probabilistic guessing.
Android: the Google Play Install Referrer API
Android has a clean, official answer: the Play Install Referrer API. Attach your payload to the Play Store URL as the referrer parameter, and Google delivers that exact string to the app on first launch.
https://play.google.com/store/apps/details?id=pro.nerdy.app&referrer=deeplink%3D%2Fproduct%2F42
val client = InstallReferrerClient.newBuilder(context).build()
client.startConnection(object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) {
if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) {
val referrer = client.installReferrer.installReferrer // "deeplink=/product/42"
DeepLinkQueue.enqueue(referrer)
client.endConnection()
}
}
override fun onInstallReferrerServiceDisconnected() {}
})
This is deterministic — the referrer string survives the store visit and the install untouched, with no server-side matching required.
Converging in one place — and waiting for login
Both platforms now feed the same in-app pending-link queue. We do this in our Flutter app development work with a small platform channel that surfaces the native payload to Dart, then a single resolver decides when to act.
The "when" matters. A deep link to an authenticated screen (/orders/42) will fail on a cold start if the user is not logged in yet. So the resolver does not navigate immediately — it holds the destination until the app is ready and any required business event has fired.
class DeepLinkQueue {
Uri? _pending;
bool _authReady = false;
void enqueue(Uri link) { _pending = link; _tryResolve(); }
// Called by the business layer, e.g. after a successful login
void onEvent(AppEvent event) {
if (event == AppEvent.loggedIn) _authReady = true;
_tryResolve();
}
void _tryResolve() {
final link = _pending;
if (link == null || !_authReady) return; // wait for both
_pending = null;
router.go(link.path); // safe: app is installed AND the user is logged in
}
}
The result is a deferred deep link that is robust against the two failure modes that break most implementations: the app not being installed, and the target screen not being ready.
When this approach is the right fit
This pattern shines when you control both the link source and the app, want first-party reliability, and care about privacy or vendor cost. If you only need the installed-app case, plain Universal Links and App Links are enough. If you need cross-channel marketing attribution with dashboards, a paid platform like Branch may be worth the spend. But for product flows — invites, shared content, onboarding, password resets — App Clips plus the Install Referrer API give you a deterministic deferred deep link you fully own.
If you are migrating off Firebase Dynamic Links or building this from scratch, we can help you ship it.
Frequently asked questions
Deferred deep linking preserves a link target across an app install. When a user taps a link without the app installed, the destination is remembered, the user installs the app, and on first launch the app routes to the original target instead of a generic home screen.
