# Deep links, deferred deep links and web-to-app

A deep link has a product job: open the right content safely. Attribution has a measurement job: describe where the install or open came from. Test them separately. Facts checked against live docs on 2026-10-17.

## Contents

- iOS Universal Links
- Android App Links
- Firebase Dynamic Links are gone
- Deferred deep links
- Route contract
- Web-to-app patterns
- What to record

## iOS Universal Links

- The app's Associated Domains entitlement lists `applinks:<domain>` entries without paths, query strings or a trailing slash. `*.` matches subdomains; otherwise every subdomain needs its own entry and its own file.
- The `apple-app-site-association` file sits at `https://<domain>/.well-known/apple-app-site-association`, served over HTTPS with a valid certificate and no redirects.
- Since iOS 14, devices fetch the file from Apple's CDN, not your server. The CDN requests it within 24 hours; devices check for updates about once a week after install. A fixed file can take days to reach users.
- During development, the entitlement's alternate mode can bypass the CDN for a private domain. Check the entitlement reference for the mode values.

## Android App Links

- Add `android:autoVerify="true"` to at least one intent filter. On Android 6.0 and later the system then checks `https://<host>/.well-known/assetlinks.json` for each host.
- On Android 12 and later, reset and re-run verification with `adb shell pm set-app-links --package PACKAGE_NAME 0 all`, then `adb shell pm verify-app-links --re-verify PACKAGE_NAME`, then read results with `adb shell pm get-app-links PACKAGE_NAME`. Any state other than `verified` means the domain did not verify.
- Only one app at a time can be verified for a domain. On Android 11 and lower, the app becomes the default handler only if every host in the manifest verifies.

## Firebase Dynamic Links are gone

- Firebase Dynamic Links shut down on 25 August 2025. Links return HTTP 404 and `.page.link` domains are no longer available.
- Inventory old links in lifecycle emails, QR codes on printed material, referral programmes, help articles and app store listings. Replace them and record the replacement domain owner.
- Before distributing links on a vendor-owned domain, record what happens to those links if the contract ends.

## Deferred deep links

- Route on the routing values the link vendor returns, never on campaign fields. AppsFlyer's Unified Deep Linking, for example, returns only `deep_link_value` and `deep_link_sub1` to `deep_link_sub10` for new users on iOS and Android; `media_source`, `campaign` and similar fields return null in that callback.
- Get attribution from the attribution path (MMP callback or raw data), not from the deep link callback.
- On Android without a vendor, the Play Store `referrer` value can carry a route key for the app to read through Install Referrer on first launch.
- The route value is an instruction to the app, not data storage. Keep secrets and personal data out of every shareable URL, and authorize access on open: a link must never grant access to someone else's private item.

## Route contract

The contract has two dimensions. Test every app state from every entry context the app's links actually use, and record outcomes by OS, browser, entry context and app state.

App states:

- Installed, signed in: reaches the intended item.
- Installed, signed out: authenticates and then reaches the item.
- Not installed: store, install, first launch, then the item (deferred).
- Expired offer or missing item: a useful fallback screen, not a crash or blank view.
- Another person's private item: access denied, with a safe fallback.
- Desktop visitor: the web destination.

Entry contexts:

- A tap inside a social or messaging app's in-app browser.
- A tap in an email client, through the email platform's click-tracking redirect.
- The URL pasted into the browser address bar.
- A tap on a page of the same web domain as the link.
- A QR code scanned with the camera app.
- A tap in the default browser and in a notes or messages app, as the control.

Universal Links and App Links do not open the app the same way in every context, and the behaviour changes with OS and host-app versions. Ask the link vendor how it handles in-app browsers and click-tracking redirects, record the fallback it uses in each, and re-test after OS releases.

## Web-to-app patterns

Pick the mechanism per platform and write it in the measurement map:

- Paid web ad in Safari to an iOS install: SKAdNetwork for Web Ads, registered by the ad network, iOS 16.1 and later, Safari only (not `SFSafariViewController` or `WKWebView`). Aggregated and delayed like app-to-app postbacks.
- Owned web page or smart banner to the App Store: App Store Connect campaign links with `pt` (provider token, one per developer account), `ct` (campaign token, up to 30 alphanumeric characters and spaces) and `mt`. A first-time download within 24 hours of the link counts; the most recent link gets credit; metrics appear only at 5 or more and after 24 hours. Aggregate only, in App Store Connect.
- Owned web page to Google Play: the `referrer` parameter on the Play link, read through Install Referrer. Install-level, Android only.
- Link vendor or MMP smart links: install-level on Android through the referrer. On iOS, ask the vendor exactly which method attributes the install. Apple bans deriving a device identity from device or network properties, so any "probabilistic" iOS match is a question for the privacy owner before launch.
- Web checkout then app login: the purchase is attributed on the web side; the app install has its own source. Join them through the account ID at login and keep `purchase_channel` on the revenue event.

## What to record

On each install or app open record `attribution_method` (`adservices`, `skan`, `aak`, `skan_web`, `install_referrer`, `app_store_campaign`, `mmp_click`, `link_open`, `unknown`), `link_domain`, `route_key`, the referring app where the link vendor reports it, and whether the route resolved, fell back or failed. This makes "the link works but the campaign shows no installs" a query rather than an argument.

Failure to watch: paid social links open the web page or the store instead of the app. Detect: link-open rate by referring app far below other sources for the same link.
