Skip to content
Tolinku
Tolinku
Sign In Start Free
Deep Linking · · 6 min read

Link Resolution Flow: How Platforms Resolve Deep Links

By Tolinku Staff
|
Tolinku industry trends dashboard screenshot for deep linking blog posts

When a user taps a link, the operating system decides what happens in milliseconds. Does the link open the browser? An app? A disambiguation dialog? The decision depends on the link type, domain verification status, user preferences, and the context of the tap. This guide explains exactly how iOS and Android make that decision.

For the full deep link lifecycle, see the deep linking lifecycle: from click to content. For the technical overview, see how deep linking works: a technical overview.

Decision Flow

When a user taps an HTTPS link on iOS:

User taps HTTPS link
  ↓
Is the tap from an in-app browser (WebView)?
  ├─ Yes → Open in WebView (Universal Links do not activate)
  └─ No ↓

Has the user previously chosen "Open in Safari" for this domain?
  ├─ Yes → Open in Safari
  └─ No ↓

Is there a cached AASA for this domain?
  ├─ No → Open in Safari
  └─ Yes ↓

Does the URL path match an entry in the AASA?
  ├─ No → Open in Safari
  └─ Yes ↓

Is the associated app installed?
  ├─ No → Open in Safari
  └─ Yes → Launch the app with the URL

AASA Path Matching

iOS matches the URL path against the AASA's components (modern format) or paths (legacy format):

{
  "applinks": {
    "details": [
      {
        "appIDs": ["TEAMID.com.example.app"],
        "components": [
          { "/": "/products/*" },
          { "/": "/offers/*" },
          { "/": "/admin/*", "exclude": true }
        ]
      }
    ]
  }
}

Matching rules:

  • * matches any substring within a path segment.
  • ? matches a single character.
  • Patterns are evaluated in order; first match wins.
  • exclude: true prevents a match, even if a previous pattern would match.
  • Matching is case-sensitive by default (set caseSensitive: false to change).

Example matches:

URL Pattern Matches?
/products/abc /products/* Yes
/products/abc/reviews /products/* No (only one level)
/products/abc/reviews /products/*/reviews Yes
/admin/settings /admin/* (excluded) No (excluded)
/offers/summer /offers/* Yes
/unknown/path (no match) No

User Override

iOS allows users to override Universal Links:

  1. When a Universal Link is active, long-pressing the link shows "Open in Safari" and "Open in [App]."
  2. If the user chooses "Open in Safari," iOS remembers this preference for the domain.
  3. Subsequent taps on links from that domain open in Safari instead of the app.
  4. The user can reverse this by long-pressing again and choosing "Open in [App]."

This preference is stored per-domain and persists until the user changes it or the app is reinstalled.

In-App Browser Behavior

Universal Links do not activate inside WebViews (in-app browsers). This affects:

App Opens links in Universal Links?
Safari System browser Yes
Chrome (iOS) System browser Yes
Messages System browser Yes
Mail Depends on settings Usually yes
Gmail In-app browser No
Facebook In-app browser No
Instagram In-app browser No
Twitter/X In-app browser No
LinkedIn In-app browser No

For in-app browsers, use a bounce page strategy (see common deep linking challenges).

Decision Flow

When a user taps an HTTPS link on Android:

User taps HTTPS link
  ↓
Is there an app with a verified App Link for this domain?
  ├─ Yes → Launch the app with the URL
  └─ No ↓

Is there an app with an unverified intent filter for this URL?
  ├─ Yes → Show disambiguation dialog
  │         "Open with: Chrome / [App]"
  └─ No → Open in Chrome (or default browser)

Intent Filter Matching

Android matches URLs against Intent filters declared in the app's manifest:

<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"
          android:host="app.example.com"
          android:pathPattern="/products/.*" />
</intent-filter>

Matching rules:

  • scheme, host, and pathPattern must all match.
  • pathPattern uses regex-like syntax: .* matches any characters.
  • pathPrefix matches the beginning of the path.
  • path matches the exact path.
  • Multiple <data> elements are OR-ed together.

Verification (autoVerify="true"):

  • If present and verification succeeds, the app opens automatically (no dialog).
  • If absent or verification fails, Android shows a disambiguation dialog.
  • On Android 12+, unverified links may open in the browser by default instead of showing a dialog.

Verified vs Unverified

State autoVerify assetlinks.json Behavior
Verified true Valid App opens automatically
Unverified true Invalid/missing Browser opens (Android 12+) or dialog
No verification false N/A Disambiguation dialog
No intent filter N/A N/A Browser opens

Multiple Apps

If multiple apps declare intent filters for the same URL:

  • Both verified: Should not happen (only one app should be verified per domain).
  • One verified, one not: Verified app wins.
  • Neither verified: Disambiguation dialog shows both apps and the browser.

Default App Settings

Users can set default apps for specific links in Android Settings > Apps > Default apps > Opening links. If a user sets Chrome as the default for a domain, your app will not open even if it has a verified App Link.

Custom URL Scheme Resolution

iOS

User taps myapp://path
  ↓
Is "myapp" registered by any installed app?
  ├─ No → Error: "Safari cannot open the page"
  └─ Yes ↓

Is there only one app registered for "myapp"?
  ├─ Yes → Launch that app
  └─ No → Undefined behavior (one of them opens)

Android

User taps myapp://path
  ↓
Is there an intent filter for "myapp" scheme?
  ├─ No → Error: "No app found"
  └─ Yes ↓

Is there only one app?
  ├─ Yes → Launch that app
  └─ No → Disambiguation dialog

Custom URL schemes have no verification mechanism, so any app can claim any scheme. This is why Universal Links and App Links are preferred.

Resolution Priority

When multiple deep link mechanisms are available, they have a priority order:

iOS Priority

  1. Universal Links (highest priority when active)
  2. Custom URL schemes (if Universal Link not available)
  3. Web fallback (if neither works)

Android Priority

  1. Verified App Links (highest priority, no dialog)
  2. Unverified intent filters (disambiguation dialog)
  3. Custom URL schemes (if not an HTTPS link)
  4. Web fallback (default browser)

Edge Cases

Same-Domain Navigation

On iOS, Universal Links do not activate when navigating within the same domain. If the user is on https://app.example.com/page1 and taps a link to https://app.example.com/products/123, the navigation stays in the browser.

Universal Links only activate when the user taps a link from a different domain.

JavaScript Navigation

Programmatic navigation via JavaScript (window.location.href = '...') does not trigger Universal Links on iOS. Only user-initiated taps trigger them.

On Android, window.location.href to an App Link URL may or may not trigger the app, depending on the browser and timing. It is unreliable.

Redirect Chains

User taps: https://tracking.com/click?url=https://app.example.com/products/123
  ↓
Browser loads tracking.com (not your domain, Universal Links do not activate)
  ↓
tracking.com redirects to app.example.com/products/123
  ↓
iOS: Safari loads the page (too late for Universal Links)
Android: Chrome may activate App Links on the redirect target (sometimes)

This is the link wrapping problem. See link wrapping and redirects: impact on deep links for solutions.

App Clip / Instant App

On iOS 14+, if an App Clip is configured for the domain, it may open instead of the full app if the full app is not installed. On Android, Instant Apps can provide a similar experience.

Debugging Resolution

iOS

# Check AASA from Apple's CDN
curl -s "https://app-site-association.cdn-apple.com/a/v1/yourdomain.com" | python3 -m json.tool

# Check local AASA cache (on-device)
# Settings > Developer > Universal Links > Diagnostics

Android

# Check verification status
adb shell pm get-app-links com.example.app
# Status should be "verified"

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

# Check which app handles a URL
adb shell pm resolve-activity -a android.intent.action.VIEW \
  -d "https://app.example.com/products/123" -c android.intent.category.BROWSABLE

Tolinku handles link resolution by hosting AASA and assetlinks.json files, managing domain verification, and providing fallback pages. See the Universal Links documentation for iOS resolution details.

For the lifecycle, see the deep linking lifecycle: from click to content. For the full guide, see the complete guide to deep linking in 2026.

Get deep linking tips in your inbox

One email per week. No spam.

Ready to add deep linking to your app?

Set up Universal Links, App Links, deferred deep linking, and analytics in minutes. Free to start.