On this page
scope
The vocabulary in the deep-linking space is sloppy. Product managers, marketers, and even mobile engineers conflate "universal link," "deep link," "app link," and "smart link" in ways that produce real production bugs. This guide draws the line precisely on iOS and Android, and shows the consequences of mixing them up.
The hub for the broader Cluster C technical authority series is /guides/firebase-dynamic-links-replacement. For the three-way distinction including Android App Links, the dedicated page is /guides/universal-link-vs-deep-link-vs-app-link.
the canonical distinction
A deep link is, in the original sense, any URL that opens a specific in-app screen rather than the app's launch screen. The mechanism on iOS is a custom URL scheme like yourapp://product/123. On Android it's an Intent filter for a custom scheme.
A Universal Link (iOS-specific term, Apple's spelling) is an HTTPS URL that opens an iOS app via system-level association with a domain you control. It is verified by the AASA file served at https://yourdomain.com/.well-known/apple-app-site-association.
These are not synonyms. They are different mechanisms with different fallback behavior, different security models, and different platform support.
| Property | Custom-scheme deep link | iOS Universal Link |
|---|---|---|
| URL form | yourapp://path |
https://yourdomain.com/path |
| Works if app NOT installed | No (error or "page not found") | Yes (falls back to Safari rendering the URL) |
| Verified ownership of the URL | No (any app can claim a scheme) | Yes (via AASA file on your domain) |
Renders in HTML <a href> |
Inconsistent — many parsers strip it | Yes (looks like any HTTPS link) |
| Behavior in social-app in-app browsers | Suppressed; the host webview keeps it | Suppressed in many webviews; degrades to web fallback |
| Required iOS framework | None (just URL handling) | associated-domains entitlement |
| Required Android counterpart term | "deep link" (Android Intent filter for custom scheme) | "App Link" (Intent filter with android:autoVerify="true") |
The Android equivalent of an iOS Universal Link is an App Link, not a "deep link." Mixing those terms is the most common confusion.
why custom schemes lost
Custom URL schemes (yourapp://) have three defects that universal links / app links resolved:
Ownership is not verified. Any iOS app can register
myapp://as a scheme. If two apps register the same scheme, behavior is undefined. Apple's LSApplicationQueriesSchemes restricts schemes an app can query, but registration is still loosely policed.They fail when the app isn't installed. A user tapping
yourapp://product/123on a device without the app gets a system-level error dialog, not a useful web fallback.They're stripped by content filters. Many HTML sanitizers, email clients, and social-share processors strip non-
https://URL schemes. The link silently disappears from the rendered page.
Universal Links and App Links fixed all three by making the URL a plain HTTPS link that the system intercepts when an app claims it via a verified association file. The web fallback is automatic — the same URL works in a browser if the app isn't installed.
iOS Universal Link mechanics
Apple's spec is here. The flow:
- Your app declares an
Associated Domainsentitlement:applinks:yourdomain.com. - Your domain serves a JSON file at
/.well-known/apple-app-site-associationcontaining your app's bundle ID and the URL paths your app handles. Format details: /guides/aasa-file-explained. - On first launch (or app install), iOS fetches the AASA file from your domain via an Apple-internal CDN proxy.
- When the user taps a matching HTTPS URL anywhere on the device, iOS routes it to your app via
application(_:continue:restorationHandler:)instead of opening Safari. - If the app isn't installed, Safari opens the URL as a normal web page.
Setup walkthrough: /guides/ios-universal-links-setup.
Failure modes:
- AASA file not served as
application/json→ iOS silently skips it. - AASA file behind a redirect → iOS rejects it.
- Path components in AASA don't match the URL being tapped → falls through to Safari.
- Link tapped inside a
WKWebView(e.g., Instagram or TikTok in-app browser) → host app captures the navigation; Universal Links don't trigger. This is the consumer-side failure category covered by the in-app browser thesis. - iOS cached an old AASA after an app update → user must reinstall to refresh.
Android App Link mechanics
The Android analogue, documented here. The flow:
- Your app declares an
<intent-filter android:autoVerify="true">for an HTTPS scheme and your domain. - Your domain serves a JSON file at
/.well-known/assetlinks.jsoncontaining your app's package name and SHA-256 cert fingerprint. Format details: /guides/assetlinks-json-explained. - On app install, Android verifies ownership by fetching
assetlinks.jsonand confirming the fingerprint matches the installed app's signature. - Verified URLs open the app directly. Unverified URLs surface the system "disambiguation" sheet ("Open with").
Setup walkthrough: /guides/android-app-links-setup.
The Android-specific intent URL pattern, used to bridge web-to-app when App Links aren't verified or available, is /guides/intent-url-android.
when each works in production
| Surface | Custom scheme yourapp:// |
iOS Universal Link / Android App Link |
|---|---|---|
| Tap from Mail | Often works (iOS), variable (Android) | Works |
| Tap from Notes | Works | Works |
| Tap from Safari address bar | User must type scheme; rare | Works |
Tap from <a href> in web page |
Often blocked by browser | Works |
| Tap inside Instagram / TikTok in-app browser | Blocked by host webview | Blocked or degraded by host webview |
| Tap from QR scan via system camera | Works | Works |
| Tap from third-party QR scanner | Variable | Works |
| Tap from inside a different app's share sheet | Works if scheme registered | Works |
| Cold install with deferred routing | No (scheme not preserved) | No (requires install-referrer / SDK on top) |
The in-app browser row is the practical headache. Both mechanisms are degraded inside WKWebView instances on iOS and WebView on Android, because the host app's webview can choose whether to surface the system handoff at all. The platform behavior is technically working as designed; the user-side impact is that links don't open the destination app from inside Instagram or TikTok.
Consumer-side breakdown: /guides/in-app-browser-logged-out. Webview-specific cookie isolation context: /guides/in-app-browser-cookies-explained.
decision: which to use, when
For new app integrations in 2026:
- Use Universal Links / App Links. They're the supported, web-fallback-capable, platform-verified mechanism. Custom URL schemes are legacy.
- Keep a custom scheme as a fallback for in-app navigation within your own app (e.g., a
yourapp://settingsURL embedded in a push notification payload). The scheme is fine for app-internal routing; it's the cross-app and web-to-app cases where it fails. - Don't ship a custom scheme alone as your primary deep-link strategy for inbound web traffic. The behavior degrades too much in the surfaces that matter (email, web, social).
For migration from Firebase Dynamic Links specifically, the playbook is /guides/firebase-dynamic-links-migration.
what about "smart links"
"Smart link" is a marketing term, not an engineering term. It usually refers to a vendor-branded URL — yourapp.app.link, yourbrand.onelink.me, yourapp.adj.st — that wraps either a Universal Link / App Link or a server-side branching service.
Underneath, a smart link is one of:
- A Universal Link / App Link to the vendor's domain, that the vendor's app SDK intercepts and resolves to your destination.
- A plain HTTPS URL that returns a redirect to your destination, with the vendor's server reading User-Agent and serving the right destination type.
- Both, hybrid.
Smart links solve three real problems Universal Links don't:
- Deferred deep linking (preserving the destination across a cold install).
- Preview metadata bundling (OG tags rendered server-side).
- Attribution (who tapped, when, on what surface).
If you don't need those three things, you don't need a smart-link SaaS. The deferred-deep-linking problem specifically: /guides/deferred-deep-linking-explained.
a working iOS example
// AppDelegate.swift — handle the Universal Link
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL,
let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let host = components.host,
host == "yourdomain.com" else {
return false
}
// route by path
if components.path.hasPrefix("/product/") {
let id = components.path.replacingOccurrences(of: "/product/", with: "")
navigateToProduct(id: id)
return true
}
return false
}
And the Android equivalent:
// MainActivity.kt — handle the App Link
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
val data: Uri = intent.data ?: return
if (data.host == "yourdomain.com" && data.pathSegments.firstOrNull() == "product") {
val id = data.pathSegments.getOrNull(1) ?: return
navigateToProduct(id)
}
}
Both expect the AASA / assetlinks.json file to be live at the time the link is tapped. Without it, the URL falls back to web.
related dev reading
- The three-way distinction: /guides/universal-link-vs-deep-link-vs-app-link
- iOS Universal Link setup: /guides/ios-universal-links-setup
- Android App Link setup: /guides/android-app-links-setup
- AASA file format: /guides/aasa-file-explained
- assetlinks.json format: /guides/assetlinks-json-explained
- Intent URL handoff on Android: /guides/intent-url-android
- Cluster C hub: /guides/firebase-dynamic-links-replacement
- Bridge to consumer-side problem: /guides/in-app-browser-logged-out
If you're evaluating no-SDK hosted link infrastructure for the non-app side of your stack, the contract surface is at link.boo/api.
references
- Apple "Supporting Universal Links in Your App" documentation
- Apple App Site Association format specification
- Android "Verify Android App Links" documentation
- Android Digital Asset Links protocol specification