How to set up deferred deep linking on iOS
iOS has no install referrer, so deferred deep links need another way through the App Store. Associated Domains, the SDK, the click token, and testing.

A user taps your link, doesn't have your app, installs it from the App Store — and the first open lands on the product, invite or offer the link was about. That's deferred deep linking, and on iOS it takes more setup than on Android, because the App Store passes nothing from the tap to the first launch.
This guide covers the whole path on iOS with LinkTrail's SDK: the domain setup, the code, how the click survives the install, and how to test it without fooling yourself.
Why is deferred deep linking harder on iOS?
On Android, Google Play hands the app a referrer string through the Install Referrer API, so the first launch can be matched to the click exactly. iOS has no equivalent: the App Store doesn't forward anything from the link to the app.
That leaves a few options. The advertising identifier needs the user's permission under App Tracking Transparency and LinkTrail doesn't use it. Comparing device characteristics is fingerprinting, which needs consent and which Apple restricts. What LinkTrail uses first is a click token: the tapped link leaves a short token on the clipboard, and the app reads it back on first launch. Probabilistic matching exists as an optional, consent-gated fallback — the methodology page documents exactly what it compares.
What do you need before you start?
- Your app registered in the LinkTrail dashboard (Apps → Register app), with its Apple Team ID and bundle ID.
- A link domain — your free *.linktrail.io subdomain or a custom domain. LinkTrail generates and hosts the apple-app-site-association file for it.
- An API key (Settings → API Keys).
- A device running iOS 16 or later for the paste control, which is the default way the click token is read.
Step 1: Add Associated Domains
In Xcode, add the Associated Domains capability to your app target and list your link domain. This is what lets iOS open your app, instead of Safari, when someone taps a link on that domain:
applinks:yourapp.linktrail.ioWhile developing, adding ?mode=developer to that entry makes a device with Associated Domains Development switched on fetch the file from the server directly instead of through Apple's CDN. Remove it before release.
Step 2: Add the SDK and handle links
Add the SDK with Swift Package Manager or CocoaPods — the iOS SDK page has both. Then configure it at launch and register one handler. The same onLink callback fires for deferred links on first launch and for links opened while the app is installed:
import LinkTrailSDK
// At launch — SwiftUI App.init or AppDelegate. configure throws on a missing key.
try LinkTrail.configure(apiKey: "lt_live_...")
LinkTrail.shared?.onLink { link, source in
// source says whether this is a deferred (first-launch) or re-engagement link
router.route(to: link.path, customData: link.customData)
}
LinkTrail.shared?.onError { error in
// An invalid key arrives here, not from configure — log it while testing
}Step 3: Forward Universal Links
When the app is already installed, iOS delivers the tapped URL to your app, and you pass it to the SDK. In SwiftUI:
.onOpenURL { LinkTrail.shared?.handleDeepLink($0) }
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
if let url = activity.webpageURL { LinkTrail.shared?.handleDeepLink(url) }
}With UIKit, do the same from your SceneDelegate's scene(_:continue:) and scene(_:openURLContexts:). handleDeepLink returns false for URLs that aren't LinkTrail links, so your own routing can take over.
Step 4: Recover the click on first launch
This is the step that makes the deferred path work on iOS, and the one most often missed. By default, the SDK reads the click token only when the user taps a paste control — Apple's UIPasteControl, which the SDK wraps as LinkTrailPasteButton. The tap is the user's permission, so iOS shows no 'Allow Paste' alert.
import LinkTrailSDK
import SwiftUI
// The install waits for the tap, so don't track it automatically.
try LinkTrail.configure(
apiKey: "lt_live_...",
options: LinkTrailOptions(autoTrackInstall: false,
clickTokenSource: .pasteButton)
)
// On your first-launch screen:
if #available(iOS 16.0, *) {
LinkTrailPasteButton()
}Once the user taps it, the SDK reads the token, tracks the install, and onLink delivers the destination. You can choose how the token is read:
| clickTokenSource | What happens | Trade-off |
|---|---|---|
| .pasteButton (default) | The token is read when the user taps the paste control | No system alert, but it needs the control on screen and autoTrackInstall: false |
| .automatic | The SDK reads the clipboard itself at first launch | No UI to build, but iOS shows the 'Allow Paste' alert |
| .none | The clipboard is never read | No alert and no UI; deferred matching falls back to probabilistic only |
Put the paste control where it makes sense to the user — a 'Continue where you left off' step on the first screen works well. UIPasteControl needs iOS 16; on iOS 15, use .automatic or .none.
Step 5: Handle consent
Consent gating is on by default. On iOS, links route immediately and attribution waits: call setConsent(true) once your consent prompt is answered, and the install is attributed without re-routing a user who's already on their screen. Routing a user to the right screen doesn't need the advertising identifier, so none of this depends on App Tracking Transparency.
LinkTrail.shared?.setConsent(true) // attributes the install, flushes queued events
LinkTrail.shared?.setConsent(false) // revoke at any timeHow do you test it?
- 1Delete the app from the test device.
- 2Put your link in Notes and tap it — don't paste it into Safari's address bar, which never opens an app.
- 3Install the build you're testing and open it.
- 4Tap the paste control on the first screen. The app should land on the linked screen.
To repeat the test without reinstalling, call LinkTrail.resetForTesting() in debug builds: it clears the install flag, cached attribution and device ID, so the next launch behaves like a first launch. Guard it with #if DEBUG — it must not ship.
Why isn't it working?
- Links open Safari instead of the app. The AASA file or the Associated Domains entry is wrong — run your domain through the Universal Links validator, and check the entry matches the link domain exactly.
- New installs open on the home screen. The paste control isn't rendered, or autoTrackInstall is still on with .pasteButton. Deferred installs then arrive unmatched.
- It works from Notes but not from Instagram. That's an in-app browser that never hands the URL to iOS; the fallback page has to route onward. One link for every channel covers in-app browsers.
- Nothing happens at all. An invalid API key fails silently in configure and arrives through onError — keep that handler logging while you test.
Setting up Android as well? The deferred path there runs through the Play Install Referrer instead — see the Android SDK guide.
Ahsan Ali
Writes about deep linking & attribution
Ahsan Ali works with the teams integrating LinkTrail's iOS, Android, React Native, and Flutter SDKs, which is where most of what he writes here starts: deferred deep linking, install attribution after ATT, and the parts of the mobile growth stack the category tends to leave vague.


