{"id":1827,"date":"2026-07-23T13:00:00","date_gmt":"2026-07-23T18:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=1827"},"modified":"2026-03-07T03:50:10","modified_gmt":"2026-03-07T08:50:10","slug":"debugging-deep-links","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/debugging-deep-links\/","title":{"rendered":"Debugging Deep Links: A Step-by-Step Guide"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">&quot;The deep link is not working&quot; is one of the vaguest bug reports a mobile team receives. The link could fail at any point: DNS resolution, verification file lookup, OS interception, app launch, or screen routing. This guide gives you a systematic approach to identify exactly where the failure occurs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For testing tools, see <a href=\"https:\/\/tolinku.com\/blog\/deep-link-testing-tools\/\">best deep link testing tools and how to use them<\/a>. For common challenges, see <a href=\"https:\/\/tolinku.com\/blog\/deep-linking-challenges\/\">common deep linking challenges and how to solve them<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Deep Link Resolution Flow<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A deep link goes through these stages. Failure at any stage breaks the experience:<\/p>\n\n\n\n<pre><code>1. User taps link\n2. OS checks if the domain has a verified app association\n3. If verified: OS opens the app and passes the URL\n4. App receives the URL and routes to the correct screen\n5. Screen renders with the correct content\n\nIf step 2 fails: browser opens instead (fallback)\nIf step 4 fails: app opens but shows wrong screen or home screen\nIf step 5 fails: screen loads but content is wrong or missing\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Check the Verification Files<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">iOS (AASA)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The Apple App Site Association file must be:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>At <code>https:\/\/yourdomain.com\/.well-known\/apple-app-site-association<\/code><\/li>\n<li>Served with <code>content-type: application\/json<\/code><\/li>\n<li>Returned with HTTP 200 (no redirects)<\/li>\n<li>Valid JSON with the correct <code>applinks<\/code> structure<\/li>\n<\/ul>\n\n\n\n<pre><code class=\"language-bash\"># Check status and content-type\ncurl -sI https:\/\/yourdomain.com\/.well-known\/apple-app-site-association\n\n# Expected:\n# HTTP\/2 200\n# content-type: application\/json\n\n# Check content\ncurl -s https:\/\/yourdomain.com\/.well-known\/apple-app-site-association | python3 -m json.tool\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Common AASA errors:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Error<\/th>\n<th>Symptom<\/th>\n<th>Fix<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>404 Not Found<\/td>\n<td>All Universal Links fail<\/td>\n<td>Deploy the AASA file to the correct path<\/td>\n<\/tr>\n<tr>\n<td>301\/302 Redirect<\/td>\n<td>Links open in browser<\/td>\n<td>Remove redirect, serve directly<\/td>\n<\/tr>\n<tr>\n<td><code>text\/html<\/code> content-type<\/td>\n<td>Links open in browser<\/td>\n<td>Configure server to return <code>application\/json<\/code><\/td>\n<\/tr>\n<tr>\n<td>Wrong Team ID in <code>appID<\/code><\/td>\n<td>Links open in browser<\/td>\n<td>Fix: <code>appID<\/code> must be <code>TEAMID.bundleID<\/code><\/td>\n<\/tr>\n<tr>\n<td>Missing route in <code>paths<\/code> \/ <code>components<\/code><\/td>\n<td>Specific routes fail<\/td>\n<td>Add the route pattern to the AASA<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Android (assetlinks.json)<\/h3>\n\n\n\n<pre><code class=\"language-bash\"># Check the file\ncurl -s https:\/\/yourdomain.com\/.well-known\/assetlinks.json | python3 -m json.tool\n\n# Verify via Google&#39;s API\ncurl -s &quot;https:\/\/digitalassetlinks.googleapis.com\/v1\/statements:list?source.web.site=https:\/\/yourdomain.com&amp;relation=delegate_permission\/common.handle_all_urls&quot;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Common assetlinks.json errors:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Error<\/th>\n<th>Symptom<\/th>\n<th>Fix<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Wrong package name<\/td>\n<td>App Links not verified<\/td>\n<td>Match exactly: <code>com.your.app<\/code><\/td>\n<\/tr>\n<tr>\n<td>Wrong SHA-256 fingerprint<\/td>\n<td>App Links not verified<\/td>\n<td>Use the signing certificate fingerprint, not the upload key<\/td>\n<\/tr>\n<tr>\n<td>Multiple signing certs (Play App Signing)<\/td>\n<td>Verification fails<\/td>\n<td>Include both the upload key and the Play signing key fingerprints<\/td>\n<\/tr>\n<tr>\n<td>File not at <code>\/.well-known\/<\/code><\/td>\n<td>All App Links fail<\/td>\n<td>Move file to correct location<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Check the OS-Level State<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">iOS<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">On iOS 14+, check Universal Links diagnostics:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><p><strong>Settings &gt; Developer &gt; Universal Links &gt; Diagnostics<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Enter your domain.<\/li>\n<li>Check if the AASA was downloaded successfully.<\/li>\n<li>If it shows &quot;Error,&quot; the AASA file has an issue.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Console.app (macOS)<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Connect the iOS device to a Mac.<\/li>\n<li>Open Console.app, filter by <code>swcd<\/code> (the process that manages Universal Links).<\/li>\n<li>Look for error messages about AASA download or validation.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Resetting Universal Links cache<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Reinstall the app (triggers AASA re-download).<\/li>\n<li>Or wait up to 24 hours for iOS to refresh the AASA cache.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Android<\/h3>\n\n\n\n<pre><code class=\"language-bash\"># Check App Links verification status\nadb shell pm get-app-links com.your.package\n\n# Expected output:\n# com.your.package:\n#   ID: ...\n#   Signatures: [...]\n#   Domains: yourdomain.com\n#     Status: verified  \u2190 This must say &quot;verified&quot;\n\n# If status is &quot;none&quot; or &quot;ask&quot;, verification failed\n# Re-verify:\nadb shell pm verify-app-links --re-verify com.your.package\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Check the Link Source<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Where the user clicks the link affects whether it works:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Source<\/th>\n<th>iOS Universal Links<\/th>\n<th>Android App Links<\/th>\n<th>Notes<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Safari<\/td>\n<td>Works<\/td>\n<td>N\/A<\/td>\n<td>Must be a tap on a link, not typed in address bar<\/td>\n<\/tr>\n<tr>\n<td>Chrome (iOS)<\/td>\n<td>Works<\/td>\n<td>Works<\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td>Notes app<\/td>\n<td>Works<\/td>\n<td>N\/A<\/td>\n<td>Good for testing<\/td>\n<\/tr>\n<tr>\n<td>iMessage<\/td>\n<td>Works<\/td>\n<td>N\/A<\/td>\n<td>Reliable test method<\/td>\n<\/tr>\n<tr>\n<td>Gmail app<\/td>\n<td>Blocked (in-app browser)<\/td>\n<td>Works<\/td>\n<td>iOS: opens in Gmail&#39;s WebView<\/td>\n<\/tr>\n<tr>\n<td>Instagram<\/td>\n<td>Blocked<\/td>\n<td>Partially works<\/td>\n<td>In-app browser on both platforms<\/td>\n<\/tr>\n<tr>\n<td>Facebook<\/td>\n<td>Blocked<\/td>\n<td>Partially works<\/td>\n<td>In-app browser on both platforms<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">If the link works from Notes\/iMessage but not from Gmail\/Instagram, the issue is the in-app browser, not your deep link configuration.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Check App-Side Routing<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If the app opens but shows the wrong screen (or the home screen):<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">iOS (SwiftUI)<\/h3>\n\n\n\n<pre><code class=\"language-swift\">\/\/ Check if onOpenURL is handling the link\n.onOpenURL { url in\n    print(&quot;Received URL: \\(url)&quot;)\n    print(&quot;Path: \\(url.path)&quot;)\n    print(&quot;Query: \\(url.query ?? &quot;none&quot;)&quot;)\n\n    \/\/ Check if your router handles this path\n    let handled = router.handle(url)\n    print(&quot;Handled: \\(handled)&quot;)\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">iOS (UIKit)<\/h3>\n\n\n\n<pre><code class=\"language-swift\">\/\/ Check if the app delegate or scene delegate is receiving the URL\nfunc application(_ application: UIApplication,\n                 continue userActivity: NSUserActivity,\n                 restorationHandler: @escaping ([UIUserActivityRestoring]?) -&gt; Void) -&gt; Bool {\n    guard let url = userActivity.webpageURL else {\n        print(&quot;No URL in user activity&quot;)\n        return false\n    }\n    print(&quot;Received Universal Link: \\(url)&quot;)\n    return handleDeepLink(url)\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Android<\/h3>\n\n\n\n<pre><code class=\"language-kotlin\">\/\/ Check if the Activity receives the Intent\noverride fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n\n    val action = intent.action\n    val data = intent.data\n\n    Log.d(&quot;DeepLink&quot;, &quot;Action: $action&quot;)\n    Log.d(&quot;DeepLink&quot;, &quot;Data: $data&quot;)\n    Log.d(&quot;DeepLink&quot;, &quot;Path: ${data?.path}&quot;)\n    Log.d(&quot;DeepLink&quot;, &quot;Query: ${data?.query}&quot;)\n\n    if (data != null) {\n        val handled = deepLinkRouter.handle(data)\n        Log.d(&quot;DeepLink&quot;, &quot;Handled: $handled&quot;)\n    }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Common app-side issues:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Issue<\/th>\n<th>Symptom<\/th>\n<th>Fix<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>Router does not handle the path<\/td>\n<td>Home screen shown<\/td>\n<td>Add the route to your router<\/td>\n<\/tr>\n<tr>\n<td>Route parameter extraction fails<\/td>\n<td>Wrong content shown<\/td>\n<td>Check regex\/pattern matching<\/td>\n<\/tr>\n<tr>\n<td>Screen requires authentication<\/td>\n<td>Login screen shown instead of content<\/td>\n<td>Queue the deep link, handle after login<\/td>\n<\/tr>\n<tr>\n<td>Race condition with app initialization<\/td>\n<td>Sometimes works, sometimes does not<\/td>\n<td>Ensure deep link handler runs after initialization<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Check Deferred Deep Links<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If the issue is specifically with deferred deep links (user installs the app and expects to land on a specific screen):<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Debugging the Match<\/h3>\n\n\n\n<pre><code class=\"language-typescript\">\/\/ Log the matching process on first app open\nasync function handleFirstOpen(deviceInfo: DeviceInfo) {\n  console.log(&#39;First open device info:&#39;, deviceInfo);\n\n  const match = await matchDeferredDeepLink({\n    ip: deviceInfo.ip,\n    userAgent: deviceInfo.userAgent,\n    timestamp: deviceInfo.firstOpenTimestamp\n  });\n\n  console.log(&#39;Match result:&#39;, {\n    found: match !== null,\n    confidence: match?.confidence,\n    originalUrl: match?.originalUrl,\n    clickTimestamp: match?.clickTimestamp,\n    timeDelta: match ? Date.now() - new Date(match.clickTimestamp).getTime() : null\n  });\n\n  if (match) {\n    navigateTo(match.originalUrl);\n  }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Common deferred deep link failures:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Issue<\/th>\n<th>Cause<\/th>\n<th>Fix<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td>No match found<\/td>\n<td>Too much time between click and install<\/td>\n<td>Extend matching window<\/td>\n<\/tr>\n<tr>\n<td>No match found<\/td>\n<td>IP changed (WiFi to cellular)<\/td>\n<td>Add fingerprint attributes beyond IP<\/td>\n<\/tr>\n<tr>\n<td>Wrong match<\/td>\n<td>Multiple users on same network<\/td>\n<td>Add more distinguishing attributes<\/td>\n<\/tr>\n<tr>\n<td>Match found but wrong screen<\/td>\n<td>Route parsing issue<\/td>\n<td>Fix the route handler<\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Debugging Decision Tree<\/h2>\n\n\n\n<pre><code>Deep link not working?\n\u2502\n\u251c\u2500 Does the AASA\/assetlinks.json return 200 with correct content-type?\n\u2502  \u251c\u2500 No \u2192 Fix the file hosting\n\u2502  \u2514\u2500 Yes \u2193\n\u2502\n\u251c\u2500 Does the OS show the domain as &quot;verified&quot;?\n\u2502  \u251c\u2500 No \u2192 Check Team ID, bundle ID, package name, SHA-256 fingerprint\n\u2502  \u2514\u2500 Yes \u2193\n\u2502\n\u251c\u2500 Does the app open when tapping the link in Notes\/Chrome?\n\u2502  \u251c\u2500 No \u2192 Check app installation, entitlements, Intent filters\n\u2502  \u2514\u2500 Yes \u2193\n\u2502\n\u251c\u2500 Does the app open from the original source (email, social, etc.)?\n\u2502  \u251c\u2500 No \u2192 In-app browser issue. Use a bounce page strategy.\n\u2502  \u2514\u2500 Yes \u2193\n\u2502\n\u251c\u2500 Does the correct screen show?\n\u2502  \u251c\u2500 No \u2192 Debug the app-side route handler\n\u2502  \u2514\u2500 Yes \u2192 Deep link is working. Check analytics data instead.\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Tolinku for Debugging<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku<\/a> provides real-time analytics showing open rates, fallback rates, and error rates per route, making it easy to identify which deep links are failing and why. See the <a href=\"https:\/\/tolinku.com\/docs\/troubleshooting\/common-issues\/\">troubleshooting documentation<\/a> for iOS-specific and Android-specific debugging guides.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For testing tools, see <a href=\"https:\/\/tolinku.com\/blog\/deep-link-testing-tools\/\">best deep link testing tools and how to use them<\/a>. For the complete 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>Fix broken deep links fast. Learn how to debug Universal Links, App Links, and deferred deep links with practical troubleshooting steps.<\/p>\n","protected":false},"author":2,"featured_media":1826,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Debugging Deep Links: A Step-by-Step Guide","rank_math_description":"Fix broken deep links fast. Learn how to debug Universal Links, App Links, and deferred deep links with practical troubleshooting steps.","rank_math_focus_keyword":"debugging deep links","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-debugging-deep-links.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-debugging-deep-links.png","footnotes":""},"categories":[11],"tags":[25,23,74,20,24,69,87,22],"class_list":["post-1827","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-deep-linking","tag-android","tag-app-links","tag-debugging","tag-deep-linking","tag-ios","tag-mobile-development","tag-troubleshooting","tag-universal-links"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1827","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=1827"}],"version-history":[{"count":1,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1827\/revisions"}],"predecessor-version":[{"id":1828,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/1827\/revisions\/1828"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/1826"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=1827"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=1827"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=1827"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}