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

Debugging Deep Links: A Step-by-Step Guide

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

"The deep link is not working" 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.

For testing tools, see best deep link testing tools and how to use them. For common challenges, see common deep linking challenges and how to solve them.

A deep link goes through these stages. Failure at any stage breaks the experience:

1. User taps link
2. OS checks if the domain has a verified app association
3. If verified: OS opens the app and passes the URL
4. App receives the URL and routes to the correct screen
5. Screen renders with the correct content

If step 2 fails: browser opens instead (fallback)
If step 4 fails: app opens but shows wrong screen or home screen
If step 5 fails: screen loads but content is wrong or missing

Step 1: Check the Verification Files

iOS (AASA)

The Apple App Site Association file must be:

  • At https://yourdomain.com/.well-known/apple-app-site-association
  • Served with content-type: application/json
  • Returned with HTTP 200 (no redirects)
  • Valid JSON with the correct applinks structure
# Check status and content-type
curl -sI https://yourdomain.com/.well-known/apple-app-site-association

# Expected:
# HTTP/2 200
# content-type: application/json

# Check content
curl -s https://yourdomain.com/.well-known/apple-app-site-association | python3 -m json.tool

Common AASA errors:

Error Symptom Fix
404 Not Found All Universal Links fail Deploy the AASA file to the correct path
301/302 Redirect Links open in browser Remove redirect, serve directly
text/html content-type Links open in browser Configure server to return application/json
Wrong Team ID in appID Links open in browser Fix: appID must be TEAMID.bundleID
Missing route in paths / components Specific routes fail Add the route pattern to the AASA
# Check the file
curl -s https://yourdomain.com/.well-known/assetlinks.json | python3 -m json.tool

# Verify via Google's API
curl -s "https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://yourdomain.com&relation=delegate_permission/common.handle_all_urls"

Common assetlinks.json errors:

Error Symptom Fix
Wrong package name App Links not verified Match exactly: com.your.app
Wrong SHA-256 fingerprint App Links not verified Use the signing certificate fingerprint, not the upload key
Multiple signing certs (Play App Signing) Verification fails Include both the upload key and the Play signing key fingerprints
File not at /.well-known/ All App Links fail Move file to correct location

Step 2: Check the OS-Level State

iOS

On iOS 14+, check Universal Links diagnostics:

  1. Settings > Developer > Universal Links > Diagnostics

    • Enter your domain.
    • Check if the AASA was downloaded successfully.
    • If it shows "Error," the AASA file has an issue.

    Console.app (macOS)

    • Connect the iOS device to a Mac.
    • Open Console.app, filter by swcd (the process that manages Universal Links).
    • Look for error messages about AASA download or validation.

    Resetting Universal Links cache

    • Reinstall the app (triggers AASA re-download).
    • Or wait up to 24 hours for iOS to refresh the AASA cache.

    Android

    # Check App Links verification status
    adb shell pm get-app-links com.your.package
    
    # Expected output:
    # com.your.package:
    #   ID: ...
    #   Signatures: [...]
    #   Domains: yourdomain.com
    #     Status: verified  ← This must say "verified"
    
    # If status is "none" or "ask", verification failed
    # Re-verify:
    adb shell pm verify-app-links --re-verify com.your.package
    

    Where the user clicks the link affects whether it works:

    Source iOS Universal Links Android App Links Notes
    Safari Works N/A Must be a tap on a link, not typed in address bar
    Chrome (iOS) Works Works
    Notes app Works N/A Good for testing
    iMessage Works N/A Reliable test method
    Gmail app Blocked (in-app browser) Works iOS: opens in Gmail's WebView
    Instagram Blocked Partially works In-app browser on both platforms
    Facebook Blocked Partially works In-app browser on both platforms

    If the link works from Notes/iMessage but not from Gmail/Instagram, the issue is the in-app browser, not your deep link configuration.

    Step 4: Check App-Side Routing

    If the app opens but shows the wrong screen (or the home screen):

    iOS (SwiftUI)

    // Check if onOpenURL is handling the link
    .onOpenURL { url in
        print("Received URL: \(url)")
        print("Path: \(url.path)")
        print("Query: \(url.query ?? "none")")
    
        // Check if your router handles this path
        let handled = router.handle(url)
        print("Handled: \(handled)")
    }
    

    iOS (UIKit)

    // Check if the app delegate or scene delegate is receiving the URL
    func application(_ application: UIApplication,
                     continue userActivity: NSUserActivity,
                     restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
        guard let url = userActivity.webpageURL else {
            print("No URL in user activity")
            return false
        }
        print("Received Universal Link: \(url)")
        return handleDeepLink(url)
    }
    

    Android

    // Check if the Activity receives the Intent
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        val action = intent.action
        val data = intent.data
    
        Log.d("DeepLink", "Action: $action")
        Log.d("DeepLink", "Data: $data")
        Log.d("DeepLink", "Path: ${data?.path}")
        Log.d("DeepLink", "Query: ${data?.query}")
    
        if (data != null) {
            val handled = deepLinkRouter.handle(data)
            Log.d("DeepLink", "Handled: $handled")
        }
    }
    

    Common app-side issues:

    Issue Symptom Fix
    Router does not handle the path Home screen shown Add the route to your router
    Route parameter extraction fails Wrong content shown Check regex/pattern matching
    Screen requires authentication Login screen shown instead of content Queue the deep link, handle after login
    Race condition with app initialization Sometimes works, sometimes does not Ensure deep link handler runs after initialization

    If the issue is specifically with deferred deep links (user installs the app and expects to land on a specific screen):

    Debugging the Match

    // Log the matching process on first app open
    async function handleFirstOpen(deviceInfo: DeviceInfo) {
      console.log('First open device info:', deviceInfo);
    
      const match = await matchDeferredDeepLink({
        ip: deviceInfo.ip,
        userAgent: deviceInfo.userAgent,
        timestamp: deviceInfo.firstOpenTimestamp
      });
    
      console.log('Match result:', {
        found: match !== null,
        confidence: match?.confidence,
        originalUrl: match?.originalUrl,
        clickTimestamp: match?.clickTimestamp,
        timeDelta: match ? Date.now() - new Date(match.clickTimestamp).getTime() : null
      });
    
      if (match) {
        navigateTo(match.originalUrl);
      }
    }
    

    Common deferred deep link failures:

    Issue Cause Fix
    No match found Too much time between click and install Extend matching window
    No match found IP changed (WiFi to cellular) Add fingerprint attributes beyond IP
    Wrong match Multiple users on same network Add more distinguishing attributes
    Match found but wrong screen Route parsing issue Fix the route handler

    Debugging Decision Tree

    Deep link not working?
    │
    ├─ Does the AASA/assetlinks.json return 200 with correct content-type?
    │  ├─ No → Fix the file hosting
    │  └─ Yes ↓
    │
    ├─ Does the OS show the domain as "verified"?
    │  ├─ No → Check Team ID, bundle ID, package name, SHA-256 fingerprint
    │  └─ Yes ↓
    │
    ├─ Does the app open when tapping the link in Notes/Chrome?
    │  ├─ No → Check app installation, entitlements, Intent filters
    │  └─ Yes ↓
    │
    ├─ Does the app open from the original source (email, social, etc.)?
    │  ├─ No → In-app browser issue. Use a bounce page strategy.
    │  └─ Yes ↓
    │
    ├─ Does the correct screen show?
    │  ├─ No → Debug the app-side route handler
    │  └─ Yes → Deep link is working. Check analytics data instead.
    

    Tolinku for Debugging

    Tolinku 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 troubleshooting documentation for iOS-specific and Android-specific debugging guides.

    For testing tools, see best deep link testing tools and how to use them. For the complete 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.