guides

Universal Links vs deep links: what's the actual difference

the linkboo team·8 min read·updated Mon Jun 01 2026 17:00:00 GMT-0700 (Pacific Daylight Time)
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:

  1. 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.

  2. They fail when the app isn't installed. A user tapping yourapp://product/123 on a device without the app gets a system-level error dialog, not a useful web fallback.

  3. 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.

Apple's spec is here. The flow:

  1. Your app declares an Associated Domains entitlement: applinks:yourdomain.com.
  2. Your domain serves a JSON file at /.well-known/apple-app-site-association containing your app's bundle ID and the URL paths your app handles. Format details: /guides/aasa-file-explained.
  3. On first launch (or app install), iOS fetches the AASA file from your domain via an Apple-internal CDN proxy.
  4. 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.
  5. 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.

The Android analogue, documented here. The flow:

  1. Your app declares an <intent-filter android:autoVerify="true"> for an HTTPS scheme and your domain.
  2. Your domain serves a JSON file at /.well-known/assetlinks.json containing your app's package name and SHA-256 cert fingerprint. Format details: /guides/assetlinks-json-explained.
  3. On app install, Android verifies ownership by fetching assetlinks.json and confirming the fingerprint matches the installed app's signature.
  4. 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://settings URL 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.

"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:

  1. Deferred deep linking (preserving the destination across a cold install).
  2. Preview metadata bundling (OG tags rendered server-side).
  3. 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.

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

Stop losing the click after the tap.

linkboo escapes the in-app browser so your real page loads — fast.

Start for free →