Deeplinkly

Glossary/Failure modes

Deep links in in-app browsers

Definition

An in-app browser is an embedded webview inside another app that renders links without handing them to the operating system, which prevents Universal Links and App Links from ever reaching the app that claims the domain.

This is not a misconfiguration and there is no setting that fixes it. When a webview loads a URL itself, the OS is never asked "which app handles this", so the answer your association file provides is never consulted. It is the leading cause of deep links that work in every test and fail for a large share of real users, because engineers test from Notes and users tap from social feeds.

Why the OS is never consulted

A Universal Link works because a tap asks the operating system to resolve a URL, and the OS checks its association records before handing it to a browser. An in-app browser skips the question: the host app already has a webview open, and it loads the URL into it. There is no resolution step to influence.

Which rendering context gives the OS a chance to route the URL.
ContextAsks the OS to resolve?Deep link opens the app?
Safari, Chrome, or the default browserYesYes
Notes, Messages, a plain email clientYesYes
SFSafariViewController (iOS)PartiallyNot for the app that presented it
WKWebView (iOS), the usual social in-app browserNoNo, unless the host app intercepts
Android WebViewNoNo, unless the host app overrides
Android Custom TabsYesYes — this is a real browser

The last row is the important one and the reason app behaviour varies so much. Android Custom Tabs is a genuine browser instance rendered inside another app, and it resolves App Links normally. A raw WebView looks nearly identical to a user and behaves completely differently. Which one an app chose is invisible from the outside, changes between releases, and is the whole explanation for "it works in one social app but not another".

The host app can fix this and mostly does not

On iOS, a WKWebView host can implement decidePolicyFor navigationAction and call UIApplication.open when the URL is not a plain web page. On Android, shouldOverrideUrlLoading can hand off to an intent. Apps that care about the ecosystem do this; large social apps generally do not, because keeping the user inside the app is the point.

Detecting that you are inside one

Since you cannot make the link work, the practical response is to know when you are inside a webview and change what the page does. Detection is user-agent sniffing, which is unreliable in principle and works well enough in practice because the tokens are stable and distinctive.

In-app browser detection
<script>
  // These tokens are stable enough to rely on, but they are vendor strings
  // and do change. Treat a miss as "probably a real browser" — the cost of
  // a false negative is a normal page, which is the correct fallback.
  var IN_APP = [
    'FBAN', 'FBAV', 'FB_IAB',   // Facebook
    'Instagram',
    'Line/',
    'Twitter',
    'LinkedInApp',
    'Snapchat',
    'Pinterest',
    'BytedanceWebview', 'musical_ly', // TikTok
    'KAKAOTALK',
    'WhatsApp',
    'WeChat', 'MicroMessenger'
  ];

  var ua = navigator.userAgent || '';
  var inApp = IN_APP.some(function (token) { return ua.indexOf(token) !== -1; });

  if (inApp) {
    document.documentElement.classList.add('in-app-browser');
  }
</script>

What to do with that signal depends on the platform, and the two are not symmetrical — Android has a real escape hatch and iOS does not.

The Android escape hatch: an intent URL
<!-- Android only. An intent: URL asks the system to start an activity
     rather than navigate, so it can break out of a WebView that would
     otherwise swallow an https:// link. S.browser_fallback_url is used
     when nothing handles the intent. -->
<a href="intent://example.com/products/42#Intent;
         scheme=https;
         package=com.example.shop;
         S.browser_fallback_url=https%3A%2F%2Fexample.com%2Fproducts%2F42;
         end">
  Open in the app
</a>

There is no iOS equivalent

iOS has no intent URL and no supported way for a page inside a WKWebView to force the host app to hand off. A custom URI scheme is the closest thing and is suppressed in most webview contexts, failing silently with no fallback. On iOS the only reliable answer is to ask the user to open the page in Safari.

What actually helps

Ordered by how much they are worth, which is roughly the reverse of how often they are attempted.

  1. Make the web page good. The most valuable response by a distance. If a meaningful share of taps land on the web regardless of configuration, the destination page needs to work on its own — the content visible, the action completable, the app a suggestion rather than a requirement.
  2. Show an explicit "Open in Safari" instruction when detection fires. Point at the overflow menu. It is a two-tap ask and users complete it when the reason is obvious, which is far better than a link that appears to do nothing.
  3. Use an `intent://` URL on Android. A real escape hatch with a built-in fallback, and worth doing because it covers the platform where in-app browsers are most common.
  4. Carry the destination across the install with [deferred deep linking](/deferred-deep-linking). If the user goes to the store from the web page, the context can survive even though the tap never reached the app.
  5. Do not auto-redirect to a custom scheme on page load. It is suppressed in most webviews, fails with no error, and on the platforms where it is not suppressed it produces an error dialog for users without the app.
What each response is worth, per platform.
ResponseiOSAndroid
A web page that works standaloneAlways worksAlways works
"Open in Safari/Chrome" promptThe only reliable optionWorks
intent:// URL with fallbackNot availableWorks in most webviews
Android Custom Tabs (if the host app uses them)n/aDeep links work normally
Custom scheme redirect on loadSuppressedSuppressed in most webviews
Deferred deep linkingRecovers the destination post-installRecovers the destination post-install

Testing for it

This class of failure is invisible to every command-line test, so it has to be tested the way it happens. It takes about ten minutes and is worth doing once per release rather than never.

  • Post the link to a private Instagram story or a draft post and tap it from the app.
  • Send it to yourself in WhatsApp and in a Slack DM — Slack uses a real browser handoff, WhatsApp does not, and the contrast is instructive.
  • Put it in a LinkedIn message and a TikTok bio.
  • Do all of the above on both platforms, since the same app frequently uses WebView on one and Custom Tabs on the other.
  • Record which combinations open the app. That list is your real coverage, and it will not match what your association file implies.

deep link debugger

In-app browsers are a context problem rather than a configuration one, so rule the configuration out first: if the debugger reports your domain as correctly associated on both platforms and links still fail from a social app, you have confirmed this is the cause and can stop changing files.

Open the deep link debugger

Frequently asked questions

Why don't my deep links work from Instagram or TikTok?
Those apps render links in an embedded webview that loads the URL itself rather than asking the operating system which app should handle it. Because the OS is never consulted, your association file is never checked, and the app is never offered the link. This is not a misconfiguration and no change to apple-app-site-association or assetlinks.json will fix it.
Can I make Universal Links work inside an in-app browser?
Not from your side on iOS. The host app would have to intercept the navigation in its WKWebView and hand the URL to the system, which large social apps generally choose not to do. On Android you have an escape hatch: an intent:// URL asks the system to start an activity rather than navigate, and it carries a browser fallback for devices without the app.
How do I detect an in-app browser?
By checking the user agent for vendor tokens — FBAN, FBAV, and FB_IAB for Facebook, Instagram, LinkedInApp, BytedanceWebview and musical_ly for TikTok, MicroMessenger for WeChat, and similar strings for Line, Snapchat, and WhatsApp. It is unreliable in principle but stable in practice, and a false negative simply renders the normal page, which is the correct fallback anyway.
Why do deep links work in some social apps but not others?
Because apps choose different rendering components. Android Custom Tabs is a real browser instance and resolves App Links normally, while a raw WebView does not, and the two look nearly identical to a user. The same app often uses different components on iOS and Android, and changes between releases, which is why coverage is inconsistent and cannot be relied upon.
What is the best fix for links tapped in social apps?
Make the destination web page work on its own. A meaningful share of taps will land on the web no matter how the deep linking is configured, so the page needs to show the content and let the user complete the action without the app. Beyond that, detect the webview and show an explicit prompt to open in the default browser, and use an intent:// URL on Android.
Does SFSafariViewController open Universal Links?
Not for the app that presented it. SFSafariViewController is better behaved than a raw WKWebView and handles Universal Links to other apps, but it deliberately will not bounce the user out to the app that opened it. That means an app cannot use it to route users into itself.

Related terms

  • Deep link opens the browser instead of the appA deep link opens the browser instead of the app when the operating system has not accepted the app as a verified handler for that URL, or when the tap occurred in a context that never offers the app the chance to handle it.
  • Deep links in email clientsDeep links break in email because click tracking rewrites the destination URL onto the email provider's domain, and neither iOS nor Android resolves the redirect before deciding which app may handle the tap.
  • Testing a deep linkTesting a deep link means verifying three separate things — that the operating system routes the URL to the app, that the app receives it, and that the app navigates to the right screen — each of which can pass while the others fail.