Software Engineering

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

June 4, 2026
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.

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.

ApproachPlatformReliabilityPrivacyStatus
Firebase Dynamic LinksiOS + AndroidHighAcceptableShut down Aug 2025
Device fingerprintingiOS + AndroidLow–mediumPoorDiscouraged
Clipboard matchingiOSMediumPaste banner shownFragile
App Clip + App Group (ours)iOSHighGoodRecommended
Play Install Referrer APIAndroidHighGoodOfficial

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.

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:

  1. The link opens the App Clip. iOS delivers the invocation URL through an NSUserActivity.
  2. The App Clip writes that URL (plus a timestamp) into a shared App Group container that the full app can also read.
  3. The App Clip shows an App Store overlay prompting the user to install the full app.
  4. 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.

Ilya Nixan

Ilya Nixan

Founder & Lead Developer

More from Ilya Nixan