How it works
The whole integration, with nothing hidden.
Mobile deep linking is a mechanism Apple and Google designed and document. Most of this page is therefore about how they work, not about us — which is deliberate, because you should be able to judge how much of the problem we actually solve.
Firebase Dynamic Links closed in August 2025. The replacements were built for somebody else.
When Google retired Dynamic Links, small app teams were pushed towards mobile attribution suites: contact-us pricing, a sales call, a minimum commitment, and a hundred features aimed at marketing departments that a four-person team does not have.
Most of those teams wanted one thing — a link that opens the right screen — and now pay enterprise prices for it, or maintain their own universal-link plumbing. We are building the third option, and publishing what it costs.
What happens when somebody taps a link
A universal link is an ordinary https:// address. That is the whole trick: it works as a web page for everybody, and opens the app for the people who have it.
- 1
iOS recognises the host
Your app has declared, in its entitlements, that it handles links on a particular domain. iOS has already fetched and cached a file from that domain confirming the app is allowed to.
- 2
The app opens directly
No browser, no redirect, no flash of a web page. The app receives the URL and decides which screen to show. If the app is not installed, the address opens in Safari like any other link.
- 3
The web page has to be worth landing on
For everyone without the app, the link is a web page. That is why every link here requires a web fallback address before it can be saved at all — a link that resolves to an error for most of the people who tap it is not a link.
Step one
Register your app
Creating an app allocates it a permanent host. Five characters, from an alphabet with no vowels in it — so a generated key can never spell a word, and therefore never spells an unfortunate one.
The key is never reused, even if you delete the app. A link printed on packaging outlives the account that made it, and recycling a host would mean somebody else’s links quietly resolving into your app.
curl https://app.quberoute.com/api/v1/app \
-H "Authorization: Bearer qr_sk_test_YOUR_KEY"
{
"app": {
"id": "0d6f1a3c-2f4b-4c8e-9a1d-7b2c6e5f0a11",
"name": "Northwind Retail",
"subdomainKey": "71k8c",
"linkHost": "71k8c.qbrt.app"
},
"environment": "test"
}Step two
Tell Apple your app owns the domain
iOS will not let an app claim links on a domain unless the domain agrees. Android works the same way through a different file — see below. It checks by fetching a small JSON file over HTTPS, at a fixed path, and comparing what it finds against the app asking.
You give us your bundle identifier and Apple Team ID; those two values are what the file is made of. The Team ID is the ten characters at the top right of the Apple Developer portal under Membership details.
{
"applinks": {
"details": [
{
"appIDs": ["A1B2C3D4E5.com.northwind.retail"],
"components": [
{
"/": "/*",
"comment": "Every path on this host opens the app"
}
]
}
]
}
}content-type: application/json, over HTTPS, with no redirect — iOS refuses all three otherwise. Note there is no file extension: it is apple-app-site-association, not .json.Apple caches this file for up to 24 hours. A correct setup therefore looks broken for a day, and that is the first thing to check before anything else — what it looks like, and how to tell the difference.
The same thing, on Android
App Links, and the one place they usually fail
Google’s mechanism is the same shape as Apple’s and differs in three ways that matter. We serve both files; the differences are worth knowing because they change what goes wrong.
- A different file, in a different shape. Google fetches
/.well-known/assetlinks.json, which is a JSON array where Apple’s is an object. Serving an object is the commonest mistake and it fails silently. - Every signing certificate, not one. Your app is signed differently in debug and in release, and again by Play App Signing. A build whose certificate is missing from that file opens a browser instead of your app, with no error anywhere — so the field takes a list, and the validator warns while only one is stored.
- Verification happens at install, not at tap. Correcting the file changes nothing on a device that already has the app until it is reinstalled or updated. Apple’s equivalent problem is a 24-hour cache; Android’s is a one-off check, and the remedies are different.
The compensation is that deferred linking on Android is exact rather than inferred. Google hands a freshly installed app the referrer that was on the Play Store URL it came from, so a token we attach at the click comes back to us and we know which click produced the install. Apple has no equivalent and has never built one. The full Android setup.
Step three
Handle the link in your app
Two pieces: declare the domain in your entitlements, and read the URL when it arrives. Both are Apple’s API. Nothing of ours runs inside your app, so nothing of ours can slow your launch or crash it.
applinks:71k8c.qbrt.appimport SwiftUI
@main
struct NorthwindApp: App {
@StateObject private var router = Router()
var body: some Scene {
WindowGroup {
RootView()
.environmentObject(router)
.onOpenURL { url in
router.open(path: url.path)
}
}
}
}func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return false
}
router.open(path: url.path)
return true
}What about people who have not installed the app?
They get the web page. That is the honest answer, and for most links it is the right one.
The harder version — send them to the App Store, and once they have installed and opened the app, take them to the screen the original link pointed at — is called deferred deep linking. It is the part of this problem that genuinely needs an SDK, because something has to remember the intent across an install.
Deferred deep linking and install attribution are built, and the measured match rate is published — including the bad case. Where matching cannot answer, the fallback is the web page, which is why every link is required to have one.
Live and test are separate worlds
Not a filter on a list. An API key belongs to one app and one environment, and the server reads both off the key rather than from anything in the request.
The practical effect: a test key cannot read or change live data even by accident, the same link alias can exist in both without colliding, and the dashboard shows an unmissable band across the top whenever you are looking at test.
Or read how the isolation is enforced if you would rather check before signing up.