Deep Link Not Working? Common Causes and Fixes
Deep link not working? Diagnose the nine most common causes, from in-app browsers to AASA and assetlinks errors, and fix each one with a clear checklist.
A deep link stops working for one of nine reasons: an in-app browser intercepted the tap, the iOS AASA file or Android assetlinks.json is broken, the app is not installed, a redirect chain sits in front of the link, the URI scheme is outdated, the link uses HTTP, a parameter is badly encoded, or the device's link association broke after an update. Each has a specific fix.
You built the link, you tested it on your own phone, it opened the app perfectly. Then a user (or your marketing team, or an App Store reviewer) taps the same link and lands on a mobile web page, a blank browser tab, or worse, a 404. "Deep link not working" is one of those bug reports that feels random because the same URL behaves differently depending on where it was tapped, which OS is handling it, and what happened on your server three redirects ago.
It is not random. There is a short list of failure modes, and almost every broken deep link falls into one of them. This guide walks through all nine, in rough order of how often we see them, with the exact fix for each. It is written for both sides of the problem: marketers who need links to open apps without touching Xcode, and developers debugging universal links and App Links at the configuration level. Start with the diagnostic table, find your symptom, and jump to the matching section.
Table of Contents
- Why Deep Links Fail
- Deep Link Diagnostic Table
- In-App Browsers Swallow the Link
- iOS Universal Links and AASA File Problems
- Android App Links and assetlinks.json Problems
- The App Is Not Installed
- Redirect Chains Break the Handoff
- URL-Level Mistakes That Kill Deep Links
- Broken Link Association After an App Update
- How to Test Your Deep Link
- Frequently Asked Questions
Why Deep Links Fail
Deep links fail because opening an app from a URL is a negotiation between four parties: the link itself, the app that hosted the tap, the operating system, and your server configuration. If any one of them opts out, the user gets a browser instead of your app.
A deep link is a URL that opens a specific screen inside a mobile app rather than a web page, using either a custom URI scheme (like myapp://product/42) or an HTTPS URL registered with the OS as an iOS Universal Link or Android App Link.
The negotiation matters because each party can veto the handoff. Facebook's in-app browser can simply refuse to pass the URL to the OS. iOS can decide your apple-app-site-association file is stale or malformed and silently fall back to Safari. Android 12+ can fail verification of assetlinks.json and route everything to Chrome. Your own server can insert a 301 redirect that strips the app association mid-flight. None of these failures produce an error message anywhere. The link "works", in the sense that something loads. It is just the wrong something.
That silence is why deep link bugs feel so maddening, and why the fix is diagnosis first, not trial and error. If you want the foundation before the troubleshooting, our explainer on how deep links actually work covers the full tap-to-app-screen journey, and deep link vs universal link untangles the terminology that vendors love to blur.
One honest note before the list: if you are a marketer sharing links to apps you do not own (YouTube, Amazon, TikTok, Spotify), most of the developer-side fixes below are not available to you. You cannot edit Amazon's AASA file. Skip to the in-app browser section, because an app opener link is the lever you actually control.
Deep Link Diagnostic Table
Match your symptom to the likely cause before touching any config. Most deep link failures announce themselves through a distinct pattern:
| Symptom | Likely Cause | Fix |
|---|---|---|
| Works from Notes/SMS, fails from Instagram or Facebook | In-app browser interception | Use an app opener link |
| Opens Safari instead of the app, everywhere, on iOS | AASA missing, malformed, or CDN-cached | Fix and revalidate the AASA file |
| Opens Chrome instead of the app on Android 12+ | assetlinks.json verification failed | Fix fingerprints, re-verify with adb |
| Opens the app for some users, browser for others | App not installed for some users | Add web/store fallback |
| Worked until you added a shortener or tracker in front | Redirect chain before the final URL | Remove the hop or use an app opener |
| Link does nothing, or shows "address invalid" | Dead or outdated URI scheme | Update the scheme, prefer HTTPS links |
| Opens the app but the wrong screen, or crashes | Encoding error in parameters | Percent-encode every parameter value |
| Broke for one user after they reinstalled the app | Device link association reset | Re-enable Open by default on the device |
Two patterns cover the bulk of real-world reports. If the link behaves differently depending on which app it was tapped in, you have an in-app browser problem. If it behaves the same (wrong) way everywhere on one platform, you have a configuration problem on that platform. Everything else is a variation.
In-App Browsers Swallow the Link
The most common reason a deep link does not work in the wild is that it was tapped inside Facebook, Instagram, TikTok, or another app with an embedded browser, and that browser never handed the URL to the operating system. Your configuration can be flawless and the link still dies here, because iOS only triggers Universal Links from system-level contexts like Safari, Mail, and Messages, and the social apps deliberately keep taps inside their own WebView.
This is by design. Meta and TikTok earn more the longer a user stays inside their app, so external links open in a sandboxed browser with the user logged out of everything. The OS never gets a chance to say "this URL belongs to the Amazon app." We measured this pattern across platforms while building our own routing: the exact same u2l.ai link that opens an app from iMessage will render as a web page inside the Instagram browser unless something actively breaks out of it.
The no-code fix is an app opener link. U2L AI's app opener wraps your destination in a smart link that detects the device and the hosting app, then uses the escape route that actually works in that context to launch the target app directly, with a web fallback when the app is not installed. No AASA file, no assetlinks.json, no SDK, nothing to configure on your side. For marketers this is usually the entire fix, because the links you share (a product page, a video, a playlist) point at apps whose deep link config you cannot touch anyway.
If you want the mechanics of why embedded browsers behave this way, read why links open in the in-app browser. And if your specific pain is one platform, we have dedicated walkthroughs for when an Amazon link opens in the browser and when a WhatsApp link refuses to open the app.
Developers should still fix their AASA and assetlinks files (the next two sections), because taps from Mail, Messages, Slack, and search results do respect them. Just do not expect those files to save you inside Instagram. They will not.
iOS Universal Links and AASA File Problems
On iOS, a universal link that opens Safari instead of your app almost always traces back to the apple-app-site-association (AASA) file. Apple is strict about this file, and four specific mistakes account for nearly every failure.
1. Wrong location or status code. The file must be served at https://yourdomain.com/.well-known/apple-app-site-association (no extension), returning HTTP 200 directly. A 301 or 302 on that path fails validation. Run:
curl -sI https://yourdomain.com/.well-known/apple-app-site-association
Anything other than a clean 200 means iOS never registered your domain.
2. Wrong Content-Type. The response must be application/json. Servers that treat extensionless files as application/octet-stream or text/plain are a classic silent killer, especially behind CDNs with default MIME rules.
3. Malformed JSON or wrong app ID. The appIDs value needs the format TEAMID.bundle.identifier, with your real Team ID prefix. A missing Team ID, a typo in the bundle ID, or a trailing comma that breaks JSON parsing all fail without any log output. Apple's Troubleshooting Universal Links Q&A walks through validating the format.
4. Apple's CDN cache. Since iOS 14, devices do not fetch your AASA file directly. They fetch it through Apple's CDN, which caches aggressively. You can see exactly what Apple has cached at https://app-site-association.cdn-apple.com/a/v1/yourdomain.com. If you fixed the file an hour ago and it still fails, this is why: propagation can take hours. In development, set the entitlement to applinks:yourdomain.com?mode=developer to bypass the CDN.
Two testing traps round out the iOS story. First, the simulator is unreliable for universal links; test on a physical device. Second, typing the URL into Safari's address bar will NOT open the app, by design. Paste the link into Notes and tap it instead. Our full iOS universal links guide covers setup end to end, including the associated domains entitlement that also has to be present in your production provisioning profile, not just the development one.
Android App Links and assetlinks.json Problems
On Android, an App Link that opens the browser instead of your app means verification of assetlinks.json failed, and since Android 12 the OS is unforgiving about it: failed verification sends every tap to the default browser, no disambiguation dialog, no second chance.
Work through these in order:
1. The file itself. It must live at https://yourdomain.com/.well-known/assetlinks.json, return HTTP 200 over HTTPS with Content-Type: application/json, and be reachable without redirects. An http-to-https hop or a bare-domain-to-www hop on that path fails verification.
2. The SHA-256 fingerprint. This is the number one Android cause, and it bites hardest after a key change. If you use Play App Signing (most apps do now), Google signs the released APK with a different key than your upload key. The fingerprint in assetlinks.json must be the app signing key SHA-256 from Play Console under App integrity, not the fingerprint of your local keystore. Teams hit this after key rotation too: the old fingerprint keeps verifying old installs while every new install fails, which produces the special misery of a bug only new users can reproduce. You can list multiple fingerprints in the array, so include old and new during a rotation.
3. The intent filter. The <intent-filter> in your manifest needs android:autoVerify="true", the VIEW action, BROWSABLE and DEFAULT categories, and a data element with android:scheme="https" and your host. A missing autoVerify silently downgrades your App Link to a plain deep link that shows a chooser at best. Also check that package_name in assetlinks.json matches your applicationId exactly; it is case-sensitive.
4. Re-verify after fixing. Verification runs at install time, so fixing the server does nothing for already-installed devices until you trigger a re-check:
adb shell pm verify-app-links --re-verify your.package.name
adb shell pm get-app-links your.package.name
The second command shows per-domain status; you want verified. Google's official App Links verification documentation documents every state that command can return. For the full setup from manifest to Play Console, see our Android App Links guide.
The App Is Not Installed
A deep link tapped on a device without the app installed has nowhere to go, and what happens next is entirely up to how the link was built. This one is less a bug than a missing requirement, but it generates a steady stream of "deep link not working" reports because the person testing has the app and half the audience does not.
The failure looks different per link type. A custom scheme link (myapp://...) with no app behind it either does nothing or throws an "address is invalid" error, which is about the worst possible experience you can ship. A universal link degrades more gracefully: it opens the underlying web URL in the browser. That is survivable if your web page is good, and a dead end if the URL only exists to feed the app.
The fix is a fallback strategy, decided before the tap:
- Web fallback: send app-less users to the equivalent web page. Right choice when the content works on the web.
- Store fallback: send them to the App Store or Play Store listing. Right choice when the destination only makes sense in-app.
- Deferred deep linking: store the intended destination, let the user install, then open the app to that exact screen on first launch. This is how a "get 20% off this specific product" campaign survives an install step. It requires an attribution or linking SDK to carry the context across the store visit, and we break down how the handoff works in what is deferred deep linking.
For links into third-party apps, U2L AI's app opener handles the no-app case automatically with a web fallback, so a u2l.ai link never strands the user on an error screen. If you run campaigns for your own app, decide the fallback per campaign; the store listing converts installs, the web page converts purchases, and sending everyone to one or the other is leaving something on the table.
Redirect Chains Break the Handoff
Putting a redirect in front of a universal link kills it, and this catches even experienced teams. iOS decides whether to open your app based on the URL that was tapped, not the URL at the end of a redirect chain. If the tapped link is tracker.example.com/abc and that 301s to yourapp.com/product/42, iOS checks the AASA for tracker.example.com, finds nothing, and opens the browser. Your perfectly configured domain never enters the decision.
This is the classic "it broke when marketing added click tracking" bug. The chain usually appears innocently: an email service provider wraps every link for click counting, an ad platform inserts a measurement hop, or someone shortens the campaign link with a generic shortener. Each wrapper works fine for web destinations and quietly severs the app handoff, because the app association lives on the final domain and the OS only ever saw the first one.
Android App Links have the same property: verification applies to the tapped host, and a server-side redirect happens after the OS has already committed to the browser.
You have three ways out:
- Remove the hop. Link directly to the associated domain and do your measurement on the destination instead.
- Associate the first domain. Some ESPs support hosting your AASA and assetlinks files on the tracking domain, so the tapped host itself is app-associated. It works, but it ties your app config to a vendor.
- Use a link built for app routing. An app opener does its device detection and app launch at the edge rather than relying on the OS reading an AASA file from the tapped domain, so tracking and app opening stop being in conflict. U2L AI records the click (geo, device, referrer) and still routes the tap into the app, which is exactly the combination a plain 301 chain cannot deliver.
Whichever you choose, audit with curl -sIL on your campaign links before launch. Every location: header in the output is a place where the app handoff can die.
URL-Level Mistakes That Kill Deep Links
Sometimes the infrastructure is fine and the URL itself is the bug. Three link-construction mistakes come up constantly.
Wrong or outdated URI scheme
Custom schemes are unversioned, unregistered, and unstable. Apps change them (fb:// paths have changed repeatedly over the years), retire them, or handle them differently per app version, and there is no error reporting when a scheme dies; the tap silently does nothing. If a scheme-based link that worked last quarter stopped working, assume the target app changed its scheme handling before you assume anything else. Prefer HTTPS-based universal links and App Links wherever the target app supports them, and treat schemes as a fallback. Our URI schemes explained guide covers where schemes still make sense and the formats popular apps expect today.
HTTP instead of HTTPS
Universal links and App Links only work over HTTPS, full stop. An http:// link is a web link everywhere, no matter how correct your association files are, and both Apple and Google also require the association files themselves to be served over valid HTTPS. If any campaign tooling downgrades or hardcodes http://, the app handoff is gone before the OS even looks. This one takes ten seconds to check and still ships to production regularly.
URL-encoding errors in parameters
Deep links carry parameters (product IDs, campaign tags, fallback URLs), and every one of them must be percent-encoded. The classic failure is nesting a full URL as a parameter without encoding it: the parser cuts the value at the first &, and your app opens to a broken screen or the wrong content. Spaces, #, and non-ASCII characters in parameter values cause the same class of bug. Encode every value (encodeURIComponent in JavaScript, or your platform's equivalent), and if a link passes through multiple systems, check that one of them is not double-encoding: %2520 in a final URL means two systems each encoded a space, and your app will receive the literal string %20.
Broken Link Association After an App Update
Deep links can break for one specific user because their device forgot the app-to-domain association, usually after an app update, a reinstall, an OS upgrade, or a restore from backup. Nothing is wrong with the link or your configuration; the device-level registration is stale. These are the reports that fail on the user's phone while working on all ten of yours.
On Android, the user-facing setting is Open by default. Have the user go to Settings, Apps, your app, Open by default, and check that opening supported links is enabled and your domains are listed as verified. If a user has ever tapped "Chrome, Always" on the app chooser dialog, that choice sticks until they clear the browser's defaults under Settings, Apps, Chrome, Open by default, Clear defaults. Reinstalling the app re-runs App Links verification and often resets a corrupted association.
On iOS, there is no settings panel for universal links, but the association can be toggled by behavior: if a user has ever long-pressed a universal link and chosen "Open in Safari" (or tapped the breadcrumb banner that says "open in browser"), iOS remembers that preference per domain and keeps routing that domain to Safari. The undo is the same gesture in reverse: long-press the link and choose "Open in [App]", or open the same link once from the banner Safari shows at the top of the page. Deleting and reinstalling the app forces iOS to re-fetch the AASA association as well.
For support teams, this cause is worth a canned reply, because it looks exactly like a regression ("your last update broke links!") and resolves with a 30-second settings change. Log which app version and OS version the reporter is on; a cluster of reports right after a release points back at your config (see the AASA and assetlinks sections above), while scattered single reports across versions point here.
How to Test Your Deep Link
Testing a deep link properly means testing it from the contexts your users actually tap in, on real devices, after every release. Here is the checklist we recommend running before any campaign goes live.
Step 1: Verify the association files from the command line
Run curl -sI against https://yourdomain.com/.well-known/apple-app-site-association and https://yourdomain.com/.well-known/assetlinks.json. Both must return HTTP 200 with Content-Type application/json and no redirects. For iOS, also fetch https://app-site-association.cdn-apple.com/a/v1/yourdomain.com to confirm Apple's CDN has your current file.
Step 2: Check the full redirect chain
Run curl -sIL on the exact link you plan to share, including any shortener or tracking wrapper. If any location: header appears before your associated domain, the universal link handoff will fail from that link. Fix the chain or switch to an app opener link.
Step 3: Test from a system context on real devices
Paste the link into the Notes app on a physical iPhone and into Google Keep or an SMS on a physical Android device, then tap it. Do not type it into a browser address bar (that never triggers the app on iOS) and do not trust the simulator. The app should open to the exact screen, not the home screen.
Step 4: Verify Android App Links status with adb
Run adb shell pm get-app-links your.package.name and confirm every domain reports verified. If not, fix assetlinks.json and re-run with --re-verify before testing taps again.
Step 5: Test from in-app browsers
Send the link to yourself and tap it inside Instagram, Facebook, and TikTok. This is where plain universal links fail by design, so this step tells you whether you need an app opener version of the link for social campaigns.
Step 6: Test the no-app path
Tap the link on a device without the app installed (or after uninstalling it). Confirm the fallback lands somewhere useful: the web page, the store listing, or a deferred deep link flow, never an error screen.
Step 7: Re-test after every release and key change
Repeat steps 1 to 6 after each app release, OS beta, signing key rotation, and CDN or server migration. Deep links break most often right after something else changed.
The 15 minutes this checklist takes is cheaper than diagnosing a live campaign whose clicks are quietly landing in a logged-out WebView. If most of your links point at other people's apps, you can skip half the list: create the link on U2L AI's app opener, run steps 3, 5, and 6, and you are done, because the association-file and redirect problems are handled for you.
Frequently Asked Questions
Why is my deep link not working?
The most common causes, in order: the link was tapped inside an in-app browser (Facebook, Instagram, TikTok) that blocks the app handoff; the iOS AASA file or Android assetlinks.json is missing, malformed, or failing verification; the app is not installed; or a redirect sits between the tapped URL and the associated domain. Match your symptom against the diagnostic table above to narrow it down fast.
Why does my deep link open in the browser instead of the app?
Either the OS never verified your domain association (broken AASA or assetlinks.json), the tap happened inside an embedded in-app browser that refuses to hand off to the OS, or the user previously chose "open in browser" and the device remembered it. If it fails everywhere, suspect configuration; if it only fails from social apps, it is the in-app browser, and an app opener link is the fix.
How do I test if a deep link is working?
Curl the association files for a clean 200 and correct Content-Type, check the redirect chain with curl -sIL, then tap the link from Notes on a real iPhone and from SMS on a real Android device. On Android, adb shell pm get-app-links your.package.name shows verification status per domain. Never test by typing the URL into a browser address bar; iOS deliberately does not trigger the app that way.
Why do universal links not work on iOS even though my AASA file is correct?
Usually one of four things: Apple's CDN is still serving a cached older version of the file (check app-site-association.cdn-apple.com/a/v1/yourdomain.com), the associated domains entitlement is missing from your production provisioning profile, the user long-pressed the link once and told iOS to open that domain in Safari, or you are testing in the simulator or the address bar, neither of which is reliable.
Why did Android 12 break my app links?
Android 12 made verification mandatory: if assetlinks.json fails verification for a domain, taps on that domain go straight to the default browser with no chooser dialog. The most common verification failure is a SHA-256 fingerprint mismatch, typically because the file lists your upload key instead of the Play App Signing key, or because a key rotation was not reflected in the file.
What happens when someone taps a deep link without the app installed?
A custom scheme link does nothing or shows an invalid-address error. A universal link or App Link opens the underlying web URL in the browser. To keep the destination intact through an install, you need deferred deep linking, which stores the intended screen and opens it on first launch after the store visit.
Do deep links work inside the Instagram and Facebook in-app browsers?
Standard universal links generally do not; the embedded browsers keep the tap inside their own WebView instead of handing the URL to the operating system. This is deliberate. App opener links work around it by detecting the hosting app and using an escape route that launches the target app directly.
Can I put a URL shortener in front of a universal link?
Not a generic one. The OS evaluates the domain that was tapped, so a shortener domain without your app association breaks the handoff even though the final URL is fine. Either link the associated domain directly or use a shortener designed for app routing, like an app opener link, which performs the app launch itself instead of relying on the destination domain's association.
Deep links break at four layers: the hosting app, the OS association, the URL itself, and the device. Work the diagnostic table top to bottom and you will find your failure in minutes instead of days, and the fix is nearly always one of the nine covered here. For the links you cannot fix at the source (social bios, campaigns, links into apps you do not own), skip the config entirely: create a free U2L AI account and turn any URL into an app opener link with click analytics built in.