A Universal Link or App Link that opens the browser instead of the app is one of the most common deep linking failures, and also one of the most misleading to debug, because nothing crashes and nothing logs an obvious error. The link just quietly doesn't reach the app. Here's a systematic way to track down why.
Why This Failure Mode Is So Hard to Triage
The frustrating part of this specific failure is that it produces zero error output anywhere obvious. The link opens, just in the wrong place, and there's no exception, no crash log, no console warning pointing at the actual cause. Engineers debugging this for the first time often start by adding logging to their app's own URL handling code, which is a reasonable instinct that turns out to be looking in the wrong place entirely, since the failure happens before the app ever gets involved.
Step 1: Confirm the Association File Is Actually Reachable
Both iOS Universal Links and Android App Links depend on a file hosted on your domain proving your app is authorized to handle links for that domain: apple-app-site-association for iOS, documented in Apple's Universal Links guide, and a assetlinks.json file for Android, covered in Android's App Links verification docs. Fetch each file directly in a browser or with curl and confirm it returns valid JSON with the correct content type, not a 404, not an HTML error page, not a redirect. A surprising number of deep linking failures trace back to this file being missing, malformed, or served with the wrong content type, which silently fails verification with no error surfaced to the developer.
Step 2: Confirm the App's Entitlements Match
On iOS, the app itself needs the associated domains entitlement configured to match the domain hosting the association file exactly, including the correct subdomain if applicable. On Android, the app's signing certificate fingerprint has to match what's declared in the assetlinks.json file. A mismatch here, often from testing with a debug build signed differently than the production certificate declared in the association file, causes verification to fail silently in exactly the same way a missing file does, which makes steps 1 and 2 easy to conflate during debugging if you're not checking them separately.
Step 3: Reinstall the App After Any Configuration Change
Both platforms cache association file verification at install time, meaning a change to the hosted association file or the app's entitlements often doesn't take effect until the app is uninstalled and reinstalled, not just updated. This catches people constantly during development: the fix is deployed, the file is confirmed correct via step 1, and the link still opens the browser, because the device is still relying on a verification result cached from before the fix.
Step 4: Test With the Platform's Own Diagnostic Tools
iOS provides diagnostic logging for Universal Links verification accessible through Console.app on a connected Mac, showing exactly why a given link did or didn't route to the app. Android provides a command-line verification check through adb, letting you confirm whether App Links verification succeeded for a specific package without relying on trial-and-error link tapping. Using these tools directly, rather than only observing the end-user behavior of tapping a link, surfaces the actual verification failure reason instead of leaving you to guess between several possible causes.
Step 5: Check for Competing Apps or User Preference Overrides
On Android specifically, if multiple apps have registered as handlers for the same domain, or if a user has previously chosen "always open in browser" for a link from that domain, the system respects that choice over automatic app routing. This isn't a configuration bug in your app at all, it's a user or system-level override, and confirming this is the actual cause, rather than continuing to debug your association file, saves significant wasted debugging time once you've ruled out steps 1 through 4.
Step 6: Verify the URL Pattern Matches Exactly
Both platforms match incoming URLs against declared patterns exactly, and a mismatch as small as a trailing slash, a query parameter your intent filter or entitlement doesn't account for, or an unexpected subdomain can cause a link to fail matching even though the association file and entitlements are otherwise correctly configured. Testing with the exact URL format your links actually use in production, not a simplified version typed by hand during debugging, catches this category of mismatch that's otherwise easy to miss.
Keep a Record of What You Checked
Once you do find the actual cause, write down which of these steps caught it. Deep linking configuration issues tend to recur across app versions, new build variants, or new team members setting up a fresh environment, and a short internal note saying "we hit this exact failure before, here's what it turned out to be" saves the next person a full pass through all seven steps.
Don't Rule Out a Simple Typo
After ruling out configuration and caching issues, it's worth a final sanity check on the association file's content itself: the bundle identifier, package name, or certificate fingerprint declared in the file has to match your app's actual values exactly, character for character. A single-character typo in a bundle ID, easy to introduce when copy-pasting between a developer portal and a text editor, produces exactly the same silent fallback-to-browser symptom as every other failure mode on this list, and it's often the last thing anyone checks because it feels too simple to be the actual cause.
A Note on CDN and Server Configuration Gotchas
If your association file is served through a CDN or a server with aggressive caching rules, a fix you've deployed to the origin server may not actually be reachable yet if the CDN is still serving a stale cached version, or worse, a cached 404 from before the file existed. Purging the relevant cache path explicitly, rather than assuming a deploy automatically propagates instantly everywhere, is worth doing before concluding a fix didn't work when it may simply not have reached the edge servers actually being queried during verification.
Step 7: Check for HTTPS and Redirect Issues
Both platforms require the association file to be served over HTTPS with a valid certificate, and some verification processes are strict about following redirects, meaning an association file reachable only through an HTTP-to-HTTPS redirect, or a www-to-non-www redirect, can fail verification even though the file itself is perfectly valid once you actually reach it in a browser. Testing the exact final URL, including scheme and subdomain, that the platform will request, rather than a URL you know redirects to the right place, catches this specific gap. Let's Encrypt's documentation covers certificate setup if that's the underlying gap on your domain.
Putting It Together: A Debugging Order That Actually Saves Time
Working through these six steps in order, rather than jumping straight to reading application routing code, saves substantial time because the vast majority of "deep link opens the browser instead of the app" failures are configuration and verification issues sitting entirely outside your app's own code, in the association file, the entitlements, or a cached verification state. Application-level routing logic is only worth debugging once you've confirmed, through steps 1 through 6, that the link is actually reaching the app in the first place.
For the navigation-side half of getting deep linking right, once a link successfully reaches your app, see https://137foundry.com/articles/deep-links-mobile-app-without-breaking-navigation, which covers reconstructing a coherent back stack for wherever the link is supposed to land.
Top comments (0)