Universal Links and Android App Links Explained: How Mobile Deep Linking Protocols Work (2026)

Learn how Apple Universal Links and Android App Links route mobile users directly into app content, how AASA and assetlinks.json domain verification work, and how branded links handle fallback redirects.

Key takeaways

  • Dual-sided domain verification eliminates URL hijacking: Apple Universal Links and Android App Links replace unverified URI schemes with standard HTTPS URLs validated by cryptographic handshakes: Apple's apple-app-site-association (AASA) file on iOS and Google's Digital Asset Links (assetlinks.json) on Android.
  • Direct user routing without browser dialogs: Verified HTTPS deep links bypass "Open in App?" browser confirmation prompts and Android intent picker dialogs, routing installed users straight to in-app views.
  • Fail-safe fallback mechanics: When an app is not installed, standard HTTPS links do not produce broken browser errors. Instead, they fall back to web pages, custom landing pages, or app store product pages.
  • In-app webview sandboxes require active handling: Embedded browsers inside social applications (such as Meta, TikTok, and other platforms) often intercept clicks within sandboxed webviews, breaking direct operating system routing unless handled via specialized JavaScript or trampoline routing.
  • Custom branded domains preserve link trust and tracking: Using branded subdomains (e.g., links.yourbrand.com) configured with CNAME records preserves marketing attribution parameters without breaking OS-level domain entitlement checks.

Protocol architecture: Custom URI schemes vs. standard HTTPS deep linking

Early mobile deep linking relied entirely on custom URI schemes (such as myapp://path/to/content). As documented by Android Developers, custom URI deep linking takes advantage of the intent system to route links to an app, but these links remain subject to the system disambiguation dialog and lack ownership verification. While straightforward to register in an app manifest, custom URI schemes introduce fundamental security and user experience limitations.

The problems with custom URI schemes

  1. No domain ownership verification (Collision & Hijacking): Any application can register the same custom scheme (e.g., fitnessapp://). If a user has two apps installed that claim the same scheme, the operating system cannot reliably determine which app should handle the request. Malicious apps can register competitor schemes to intercept incoming link payloads and sensitive tokens.
  2. Disambiguation prompts: Because the OS cannot confirm domain ownership, users often encounter modal confirmation dialogs ("Open with...") before navigating to the app.
  3. Broken fallback handling: If the requested app is not installed, clicking a custom URI scheme inside a standard web browser leads to a hard browser error (such as "Address unreachable" or "Cannot open page").

To solve these vulnerabilities, Apple introduced Universal Links in iOS 9, and Google introduced Android App Links in Android 6.0 (API level 23) as detailed in Android's App Links verification documentation. Both protocols bind standard web URLs (https://) directly to an application through bidirectional domain verification.

According to Apple Developer Documentation, Universal Links allow an app to present native content in place of all or part of its website, while users who do not have the app installed receive the same content in a web browser.

If the app is installed, the mobile operating system intercepts the HTTPS request and routes the user directly into native code without opening the web browser disambiguation dialog. If the app is not installed, the request falls back cleanly to the web server, which can serve the web page or redirect the user to the Apple App Store or Google Play Store.


According to Apple Developer Documentation, associated domains provide the underpinning to Apple Universal Links, allowing iOS apps to claim ownership over standard web domains. When an iOS device opens a verified Universal Link, iOS routes the user directly into the app using native API entry points.

The apple-app-site-association (AASA) file

The foundation of Universal Links is the apple-app-site-association file. As outlined by Apple, this JSON file is hosted on your web domain to define which apps and app IDs are authorized to handle specific URL paths.

{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appIDs": [
          "TEAMID1234.com.example.app"
        ],
        "components": [
          {
            "/": "/workout/*"
          },
          {
            "/": "/subscribe"
          },
          {
            "/": "/checkout/*",
            "exclude": true
          }
        ]
      }
    ]
  }
}

AASA hosting requirements

  • Path: As specified by Apple Developer Documentation, the file must be accessible at either https://<domain>/.well-known/apple-app-site-association or https://<domain>/apple-app-site-association.
  • HTTPS & Certificates: Must be served over a valid HTTPS connection with a trusted TLS certificate (self-signed certificates are rejected).
  • MIME Type: Must be served with Content-Type: application/json.
  • Redirects: The server must not return any HTTP redirects (301 or 302). iOS expects an immediate 200 OK response.
  • Apple CDN Caching: Starting with iOS 14 and macOS 11, Apple notes that apps send requests for AASA files to an Apple-managed content delivery network (CDN) rather than querying your web server directly.

Xcode Associated Domains configuration

To enable Universal Link handling in the client application, Apple Developer Documentation specifies setting up the Associated Domains entitlement:

    1. In Xcode, navigate to your app target's Signing & Capabilities.
    2. Add the Associated Domains capability.
    3. Add the domain with the applinks: prefix:
      applinks:links.example.com
      
    4. If you support Universal Links across development and production environments, you can append ?mode=developer for local debugging builds.

When a user clicks a Universal Link, UIKit calls the application delegate method:

func application(
    _ application: UIApplication,
    continue userActivity: NSUserActivity,
    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let incomingURL = userActivity.webpageURL else {
        return false
    }
    
    // Parse path components and query parameters
    let path = incomingURL.path
    let queryParams = incomingURL.query
    
    // Route to the appropriate in-app view controller
    Router.shared.navigate(to: path, params: queryParams)
    return true
}

In SwiftUI apps using iOS 14 and later, incoming URLs can be handled directly using the .onOpenURL modifier:

@main
struct SubscriptionApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    Router.shared.handle(url: url)
                }
        }
    }
}

As documented by Android Developers, Android App Links are verified HTTP/HTTPS deep links that establish a trusted association between an app and website, immediately opening corresponding in-app content without triggering the system disambiguation dialog ("Open with...").

As detailed in Android's App Links verification guide, the Android system queries the Digital Asset Links file hosted at https://<domain>/.well-known/assetlinks.json for each unique hostname declared in the intent filters.

[
  {
    "relation": [
      "delegate_permission/common.handle_all_urls"
    ],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.app",
      "sha256_cert_fingerprints": [
        "14:6D:E9:7D:0F:52:CC:45:25:4E:69:E2:E0:64:4F:E8:EE:00:2E:81:97:04:EB:37:4E:6B:43:31:8B:D8:0C:71"
      ]
    }
  }
]

Extracting your SHA-256 fingerprint

To get the SHA-256 fingerprint from your release keystore using the Java keytool command:

keytool -list -v -keystore my-release-key.keystore -alias my-key-alias

If you use Google Play App Signing, you must retrieve the SHA-256 fingerprint from the Google Play Console under Release > Setup > App Integrity > App Signing Certificate, because Google re-signs your production APK before distribution.

Configuring AndroidManifest.xml

As explained in Android Developers documentation, when android:autoVerify="true" is declared in an intent filter, installing the app on Android 6.0 (API level 23) or higher causes the OS to automatically verify the declared hosts:

<activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="singleTask">
    
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        
        <data android:scheme="https" />
        <data android:host="links.example.com" />
        <data android:pathPrefix="/workout" />
        <data android:pathPrefix="/subscribe" />
    </intent-filter>
</activity>
  • android:autoVerify="true": Instructs Android OS to fetch assetlinks.json from the specified host during installation. If verification succeeds, the OS automatically handles links from this host without prompting the user.
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    
    handleIntent(intent)
}

override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    setIntent(intent)
    intent?.let { handleIntent(it) }
}

private fun handleIntent(intent: Intent) {
    val action: String? = intent.action
    val data: Uri? = intent.data
    
    if (Intent.ACTION_VIEW == action && data != null) {
        val path = data.path
        val campaignSource = data.getQueryParameter("utm_source")
        
        // Pass route to internal navigation engine
        AppNavigator.navigate(path, campaignSource)
    }
}

Branded domain masking, in-app webview sandboxes, and fallback routing

In production growth marketing, links rarely point directly to raw app endpoints. Instead, teams use branded routing links (e.g., https://go.yourbrand.com/summer-sale) to track marketing campaigns across channels while routing users directly into the app.

Branded domain CNAME configuration

A branded deep link uses your custom domain while routing DNS traffic to your deep linking and measurement infrastructure.

    1. DNS Setup: Create a CNAME record with your DNS provider (e.g., Cloudflare, Route 53):
      CNAME  links.yourbrand.com  -->  custom.airbridge.io
      
    2. Automated Verification Hosting: The endpoint server automatically serves both /.well-known/apple-app-site-association and /.well-known/assetlinks.json for that custom subdomain, matching your app credentials.
    3. Parameter Preservation: Standard attribution parameters, such as UTM parameters detailed by Ortto, AgencyAnalytics, and Ateko, are parsed and retained throughout redirects and in-app payloads.

The in-app webview sandboxing challenge

One of the most frequent failure points in mobile deep linking occurs within social platforms (such as Meta applications like Instagram and Facebook, TikTok, or other feed-based platforms). When a user taps a link inside these apps, the host application does not pass the click to the system browser (Safari or Chrome). Instead, it loads the link inside a sandboxed in-app webview (WKWebView on iOS or embedded WebView on Android).

Operating EnvironmentiOS Universal Link BehaviorAndroid App Link Behavior
Native Safari / ChromeDirect native app open (Zero prompts)Direct native app open (Zero prompts)
Mail / Notes / MessagesDirect native app openDirect native app open
In-App WebViews (Meta, TikTok)Sandboxed in webview; direct OS routing interceptedSandboxed in webview; direct OS routing intercepted
Typing directly into URL barSafari loads webpage (Universal Link bypassed)Chrome loads webpage (App Link bypassed)

How to bypass in-app webview sandboxing

To ensure users reach the native app from sandboxed webviews, routing engines use specialized fallback mechanics:

    1. Trampoline landing pages: As suggested in Android documentation, introducing a trampoline activity or landing page allows the user to open the link in the first-party app. If a webview user-agent is detected, the server serves a lightweight intermediate page that executes a client-side JavaScript redirect or prompts an explicit user touch event.
    2. Android Intent URIs: On Android, serving an intent-based scheme (intent://path#Intent;scheme=https;package=com.example.app;end) forces the in-app webview to yield execution to the Android OS package manager.
    3. Universal Link touch triggers: On iOS, Universal Links require a distinct user interaction (such as tapping a button on a web page) if triggered from within certain iframe or script-based redirects.

Technical comparison: Protocols and capabilities

Understanding the technical boundaries between custom URI schemes, Universal Links, and Android App Links helps growth teams design resilient routing rules.

Protocol / CapabilityCustom URI Schemes (scheme://)Apple Universal Links (https://)Android App Links (https://)
Underlying ProtocolCustom URI schemeStandard HTTP/HTTPSStandard HTTP/HTTPS
Verification FileNoneapple-app-site-associationassetlinks.json
Verification LocationClient manifest only/.well-known/apple-app-site-association/.well-known/assetlinks.json
OS Verification TimingRuntimeApp install / update (via Apple CDN)App install / update (via Google verification)
Hijacking ProtectionLow (Any app can register)High (Cryptographic domain verification)High (SHA-256 fingerprint matching)
Browser PromptsModal confirmation promptNone (Direct native routing)None (Direct native routing)
App Absent FallbackHard browser errorWeb URL or App Store redirectWeb URL or Google Play redirect
Webview InterceptionFrequently blockedSandboxed without trampoline routingSandboxed without trampoline routing

Deep linking workflow and marketing attribution

When an existing subscriber clicks a promotional deep link, the link routes them directly to a target in-app view (such as an upgraded tier or seasonal challenge). If an uninstalled user clicks the link, attribution engines capture the click parameters, route the user to the app store, and attribute the subsequent install upon first launch. Post-install revenue and subscription events can then be tied back to the initial campaign, aligning with measurement models described in marketing analytics literature from Adjust and AppsFlyer.

For engineering and growth teams managing cross-platform campaigns across mobile, web, PC, console, and CTV, Airbridge provides dedicated smart links and deep linking management. Teams focusing purely on routing can use the Airbridge DeepLink Plan (free for the first 10,000 MAU, then $3 per 1,000 MAU). For full-funnel attribution, raw data export, and automated fraud detection without annual lock-in, teams can use the self-serve Airbridge Core Plan ($40+/mo with a 30-day free trial, including 500,000 data points per month and $0.0001 per additional data point).


When a Universal Link opens in Safari instead of your app, check the following common failure points:

  1. Apple CDN cache delay: Test against your domain directly by configuring developer mode (applinks:example.com?mode=developer) in your Xcode entitlements.
  2. AASA JSON formatting: Verify that apple-app-site-association contains valid JSON without trailing commas, returns a 200 OK status without redirects, and is served with Content-Type: application/json.
  3. App ID formatting: Ensure the appIDs array contains your exact TeamID.BundleIdentifier prefix.
  4. User-disabled routing: If a user taps the domain URL banner in Safari's top navigation bar, iOS disables Universal Link routing for that domain on that device. To re-enable it, long-press the link in Notes or Messages and select Open in [App Name].

You can force Android OS to execute App Link verification using Android Debug Bridge (adb) without reinstalling the app:

# Check current verification status
adb shell pm get-app-links com.example.app

# Reset verification state
adb shell pm reset-app-links com.example.app

# Force verification process
adb shell pm verify-app-links --re-verify com.example.app

Check the verification status output. According to Android Developers documentation, if the domain state returns verified, your assetlinks.json and manifest filters are properly configured; any other state indicates verification could not be performed.

Apple Universal Links are designed not to hijack internal navigation while a user is already browsing your website. If a user is on https://example.com/page-a and clicks a link pointing to https://example.com/page-b, iOS assumes the user intends to stay within the Safari browser experience. Universal Links only trigger when navigated from an external context (e.g., Mail, Notes, Messages, or a different domain/subdomain). Using a dedicated subdomain (such as links.example.com) ensures links originating from example.com trigger the app.

Yes. A single HTTPS domain (e.g., links.yourbrand.com) can host both /.well-known/apple-app-site-association and /.well-known/assetlinks.json simultaneously. As specified by Apple and Android, when an iOS device interacts with the link, it parses the AASA file; when an Android device interacts with the link, it parses assetlinks.json. Desktop web browsers that open the URL will ignore both verification files and load the default web page or configured fallback destination.

Get Started Free

See how these criteria hold up on the real thing.

Get Started Free