{"id":1851,"date":"2026-07-26T09:00:00","date_gmt":"2026-07-26T14:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1851"},"modified":"2026-03-07T03:50:13","modified_gmt":"2026-03-07T08:50:13","slug":"link-resolution-flow","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/link-resolution-flow\/","title":{"rendered":"Link Resolution Flow: How Platforms Resolve Deep Links"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For the full deep link lifecycle, see <a href=\"https:\/\/tolinku.com\/blog\/deep-linking-lifecycle\/\">the deep linking lifecycle: from click to content<\/a>. For the technical overview, see <a href=\"https:\/\/tolinku.com\/blog\/how-deep-linking-works\/\">how deep linking works: a technical overview<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">iOS Link Resolution<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Decision Flow<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When a user taps an HTTPS link on iOS:<\/p>\n\n\n\n<pre><code>User taps HTTPS link\n  \u2193\nIs the tap from an in-app browser (WebView)?\n  \u251c\u2500 Yes \u2192 Open in WebView (Universal Links do not activate)\n  \u2514\u2500 No \u2193\n\nHas the user previously chosen &quot;Open in Safari&quot; for this domain?\n  \u251c\u2500 Yes \u2192 Open in Safari\n  \u2514\u2500 No \u2193\n\nIs there a cached AASA for this domain?\n  \u251c\u2500 No \u2192 Open in Safari\n  \u2514\u2500 Yes \u2193\n\nDoes the URL path match an entry in the AASA?\n  \u251c\u2500 No \u2192 Open in Safari\n  \u2514\u2500 Yes \u2193\n\nIs the associated app installed?\n  \u251c\u2500 No \u2192 Open in Safari\n  \u2514\u2500 Yes \u2192 Launch the app with the URL\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">AASA Path Matching<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">iOS matches the URL path against the AASA&#39;s <code>components<\/code> (modern format) or <code>paths<\/code> (legacy format):<\/p>\n\n\n\n<pre><code class=\"language-json\">{\n  &quot;applinks&quot;: {\n    &quot;details&quot;: [\n      {\n        &quot;appIDs&quot;: [&quot;TEAMID.com.example.app&quot;],\n        &quot;components&quot;: [\n          { &quot;\/&quot;: &quot;\/products\/*&quot; },\n          { &quot;\/&quot;: &quot;\/offers\/*&quot; },\n          { &quot;\/&quot;: &quot;\/admin\/*&quot;, &quot;exclude&quot;: true }\n        ]\n      }\n    ]\n  }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Matching rules:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>*<\/code> matches any substring within a path segment.<\/li>\n<li><code>?<\/code> matches a single character.<\/li>\n<li>Patterns are evaluated in order; first match wins.<\/li>\n<li><code>exclude: true<\/code> prevents a match, even if a previous pattern would match.<\/li>\n<li>Matching is case-sensitive by default (set <code>caseSensitive: false<\/code> to change).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Example matches:<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>URL<\/th>\n<th>Pattern<\/th>\n<th>Matches?<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td><code>\/products\/abc<\/code><\/td>\n<td><code>\/products\/*<\/code><\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td><code>\/products\/abc\/reviews<\/code><\/td>\n<td><code>\/products\/*<\/code><\/td>\n<td>No (only one level)<\/td>\n<\/tr>\n<tr>\n<td><code>\/products\/abc\/reviews<\/code><\/td>\n<td><code>\/products\/*\/reviews<\/code><\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td><code>\/admin\/settings<\/code><\/td>\n<td><code>\/admin\/*<\/code> (excluded)<\/td>\n<td>No (excluded)<\/td>\n<\/tr>\n<tr>\n<td><code>\/offers\/summer<\/code><\/td>\n<td><code>\/offers\/*<\/code><\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td><code>\/unknown\/path<\/code><\/td>\n<td>(no match)<\/td>\n<td>No<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">User Override<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">iOS allows users to override Universal Links:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>When a Universal Link is active, long-pressing the link shows &quot;Open in Safari&quot; and &quot;Open in [App].&quot;<\/li>\n<li>If the user chooses &quot;Open in Safari,&quot; iOS remembers this preference for the domain.<\/li>\n<li>Subsequent taps on links from that domain open in Safari instead of the app.<\/li>\n<li>The user can reverse this by long-pressing again and choosing &quot;Open in [App].&quot;<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">This preference is stored per-domain and persists until the user changes it or the app is reinstalled.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">In-App Browser Behavior<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Universal Links do not activate inside WebViews (in-app browsers). This affects:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>App<\/th>\n<th>Opens links in<\/th>\n<th>Universal Links?<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Safari<\/td>\n<td>System browser<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>Chrome (iOS)<\/td>\n<td>System browser<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>Messages<\/td>\n<td>System browser<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>Mail<\/td>\n<td>Depends on settings<\/td>\n<td>Usually yes<\/td>\n<\/tr>\n<tr>\n<td>Gmail<\/td>\n<td>In-app browser<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td>Facebook<\/td>\n<td>In-app browser<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td>Instagram<\/td>\n<td>In-app browser<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td>Twitter\/X<\/td>\n<td>In-app browser<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td>LinkedIn<\/td>\n<td>In-app browser<\/td>\n<td>No<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">For in-app browsers, use a bounce page strategy (see <a href=\"https:\/\/tolinku.com\/blog\/deep-linking-challenges\/\">common deep linking challenges<\/a>).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Android Link Resolution<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Decision Flow<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When a user taps an HTTPS link on Android:<\/p>\n\n\n\n<pre><code>User taps HTTPS link\n  \u2193\nIs there an app with a verified App Link for this domain?\n  \u251c\u2500 Yes \u2192 Launch the app with the URL\n  \u2514\u2500 No \u2193\n\nIs there an app with an unverified intent filter for this URL?\n  \u251c\u2500 Yes \u2192 Show disambiguation dialog\n  \u2502         &quot;Open with: Chrome \/ [App]&quot;\n  \u2514\u2500 No \u2192 Open in Chrome (or default browser)\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Intent Filter Matching<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Android matches URLs against <a href=\"https:\/\/developer.android.com\/training\/app-links\/deep-linking\" rel=\"nofollow noopener\" target=\"_blank\">Intent filters<\/a> declared in the app&#39;s manifest:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n    &lt;action android:name=&quot;android.intent.action.VIEW&quot; \/&gt;\n    &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; \/&gt;\n    &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; \/&gt;\n    &lt;data android:scheme=&quot;https&quot;\n          android:host=&quot;app.example.com&quot;\n          android:pathPattern=&quot;\/products\/.*&quot; \/&gt;\n&lt;\/intent-filter&gt;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Matching rules:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>scheme<\/code>, <code>host<\/code>, and <code>pathPattern<\/code> must all match.<\/li>\n<li><code>pathPattern<\/code> uses regex-like syntax: <code>.*<\/code> matches any characters.<\/li>\n<li><code>pathPrefix<\/code> matches the beginning of the path.<\/li>\n<li><code>path<\/code> matches the exact path.<\/li>\n<li>Multiple <code>&lt;data&gt;<\/code> elements are OR-ed together.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Verification (<code>autoVerify=&quot;true&quot;<\/code>):<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>If present and verification succeeds, the app opens automatically (no dialog).<\/li>\n<li>If absent or verification fails, Android shows a disambiguation dialog.<\/li>\n<li>On Android 12+, unverified links may open in the browser by default instead of showing a dialog.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Verified vs Unverified<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>State<\/th>\n<th>autoVerify<\/th>\n<th>assetlinks.json<\/th>\n<th>Behavior<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Verified<\/td>\n<td>true<\/td>\n<td>Valid<\/td>\n<td>App opens automatically<\/td>\n<\/tr>\n<tr>\n<td>Unverified<\/td>\n<td>true<\/td>\n<td>Invalid\/missing<\/td>\n<td>Browser opens (Android 12+) or dialog<\/td>\n<\/tr>\n<tr>\n<td>No verification<\/td>\n<td>false<\/td>\n<td>N\/A<\/td>\n<td>Disambiguation dialog<\/td>\n<\/tr>\n<tr>\n<td>No intent filter<\/td>\n<td>N\/A<\/td>\n<td>N\/A<\/td>\n<td>Browser opens<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Multiple Apps<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If multiple apps declare intent filters for the same URL:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Both verified:<\/strong> Should not happen (only one app should be verified per domain).<\/li>\n<li><strong>One verified, one not:<\/strong> Verified app wins.<\/li>\n<li><strong>Neither verified:<\/strong> Disambiguation dialog shows both apps and the browser.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Default App Settings<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Users can set default apps for specific links in Android Settings &gt; Apps &gt; Default apps &gt; 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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Custom URL Scheme Resolution<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">iOS<\/h3>\n\n\n\n<pre><code>User taps myapp:\/\/path\n  \u2193\nIs &quot;myapp&quot; registered by any installed app?\n  \u251c\u2500 No \u2192 Error: &quot;Safari cannot open the page&quot;\n  \u2514\u2500 Yes \u2193\n\nIs there only one app registered for &quot;myapp&quot;?\n  \u251c\u2500 Yes \u2192 Launch that app\n  \u2514\u2500 No \u2192 Undefined behavior (one of them opens)\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Android<\/h3>\n\n\n\n<pre><code>User taps myapp:\/\/path\n  \u2193\nIs there an intent filter for &quot;myapp&quot; scheme?\n  \u251c\u2500 No \u2192 Error: &quot;No app found&quot;\n  \u2514\u2500 Yes \u2193\n\nIs there only one app?\n  \u251c\u2500 Yes \u2192 Launch that app\n  \u2514\u2500 No \u2192 Disambiguation dialog\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Custom URL schemes have no verification mechanism, so any app can claim any scheme. This is why Universal Links and App Links are preferred.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Resolution Priority<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When multiple deep link mechanisms are available, they have a priority order:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">iOS Priority<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Universal Links<\/strong> (highest priority when active)<\/li>\n<li><strong>Custom URL schemes<\/strong> (if Universal Link not available)<\/li>\n<li><strong>Web fallback<\/strong> (if neither works)<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Android Priority<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Verified App Links<\/strong> (highest priority, no dialog)<\/li>\n<li><strong>Unverified intent filters<\/strong> (disambiguation dialog)<\/li>\n<li><strong>Custom URL schemes<\/strong> (if not an HTTPS link)<\/li>\n<li><strong>Web fallback<\/strong> (default browser)<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Edge Cases<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Same-Domain Navigation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">On iOS, Universal Links do <strong>not<\/strong> activate when navigating within the same domain. If the user is on <code>https:\/\/app.example.com\/page1<\/code> and taps a link to <code>https:\/\/app.example.com\/products\/123<\/code>, the navigation stays in the browser.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Universal Links only activate when the user taps a link from a <strong>different<\/strong> domain.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">JavaScript Navigation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Programmatic navigation via JavaScript (<code>window.location.href = &#39;...&#39;<\/code>) does <strong>not<\/strong> trigger Universal Links on iOS. Only user-initiated taps trigger them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On Android, <code>window.location.href<\/code> to an App Link URL may or may not trigger the app, depending on the browser and timing. It is unreliable.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Redirect Chains<\/h3>\n\n\n\n<pre><code>User taps: https:\/\/tracking.com\/click?url=https:\/\/app.example.com\/products\/123\n  \u2193\nBrowser loads tracking.com (not your domain, Universal Links do not activate)\n  \u2193\ntracking.com redirects to app.example.com\/products\/123\n  \u2193\niOS: Safari loads the page (too late for Universal Links)\nAndroid: Chrome may activate App Links on the redirect target (sometimes)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This is the link wrapping problem. See <a href=\"https:\/\/tolinku.com\/blog\/link-wrapping-and-redirects\/\">link wrapping and redirects: impact on deep links<\/a> for solutions.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">App Clip \/ Instant App<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Debugging Resolution<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">iOS<\/h3>\n\n\n\n<pre><code class=\"language-bash\"># Check AASA from Apple&#39;s CDN\ncurl -s &quot;https:\/\/app-site-association.cdn-apple.com\/a\/v1\/yourdomain.com&quot; | python3 -m json.tool\n\n# Check local AASA cache (on-device)\n# Settings &gt; Developer &gt; Universal Links &gt; Diagnostics\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Android<\/h3>\n\n\n\n<pre><code class=\"language-bash\"># Check verification status\nadb shell pm get-app-links com.example.app\n# Status should be &quot;verified&quot;\n\n# Force re-verification\nadb shell pm verify-app-links --re-verify com.example.app\n\n# Check which app handles a URL\nadb shell pm resolve-activity -a android.intent.action.VIEW \\\n  -d &quot;https:\/\/app.example.com\/products\/123&quot; -c android.intent.category.BROWSABLE\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Tolinku for Link Resolution<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku<\/a> handles link resolution by hosting AASA and assetlinks.json files, managing domain verification, and providing fallback pages. See the <a href=\"https:\/\/tolinku.com\/docs\/concepts\/universal-links\/\">Universal Links documentation<\/a> for iOS resolution details.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For the lifecycle, see <a href=\"https:\/\/tolinku.com\/blog\/deep-linking-lifecycle\/\">the deep linking lifecycle: from click to content<\/a>. For the full guide, see <a href=\"https:\/\/tolinku.com\/blog\/complete-guide-deep-linking-2026\/\">the complete guide to deep linking in 2026<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Understand how iOS and Android resolve deep links at the OS level. Learn about link resolution priority, verification, and edge cases.<\/p>\n","protected":false},"author":2,"featured_media":1850,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Link Resolution Flow: How Platforms Resolve Deep Links","rank_math_description":"Understand how iOS and Android resolve deep links at the OS level. Learn about link resolution priority, verification, and edge cases.","rank_math_focus_keyword":"link resolution","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-link-resolution-flow.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-link-resolution-flow.png","footnotes":""},"categories":[11],"tags":[25,23,305,20,24,559,69,22],"class_list":["post-1851","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-deep-linking","tag-android","tag-app-links","tag-architecture","tag-deep-linking","tag-ios","tag-link-resolution","tag-mobile-development","tag-universal-links"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1851","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=1851"}],"version-history":[{"count":1,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1851\/revisions"}],"predecessor-version":[{"id":1852,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1851\/revisions\/1852"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1850"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1851"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1851"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1851"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}