How to detect an in-app browser from the user agent
Every social app marks its WebView in the user agent, which makes detection a substring test. The interesting part is what to do when the marker is missing, which now happens on Facebook.
5 August 2026 by PAD team
The markers, per platform
Each app appends its own token to an otherwise ordinary Safari or Chrome string. Version numbers move, the tokens do not.
| App | Marker in the user agent |
|---|---|
Instagram | |
FBAV, FBAN, FB_IAB | |
| Messenger | FBAN/MessengerForiOS |
| TikTok | BytedanceWebview, musical_ly |
| Snapchat | Snapchat |
LinkedInApp | |
Pinterest | |
| Twitter / X | Twitter |
| Telegram | Telegram (Android only, reliably) |
| Line | Line |
A full Instagram string on iOS looks like this. Note that everything before the last token is indistinguishable from Safari:
Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X)
AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
Instagram 320.0.0.24.107 (iPhone15,2; iOS 18_5; en_US; …)
And Facebook, on a build that still identifies itself:
Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X)
AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
[FBAN/FBIOS;FBAV/500.0.0.35.100;FBBV/…;FBDV/iPhone15,2]
A detection function
Nothing clever, and deliberately so. The one decision worth making is what the function returns: a platform name rather than a boolean, because the escape route differs per platform.
function inAppBrowser() {
var ua = navigator.userAgent || "";
if (/Instagram/i.test(ua)) return "instagram";
if (/FBAN|FBAV|FB_IAB/i.test(ua)) return "facebook";
if (/BytedanceWebview|musical_ly/i.test(ua)) return "tiktok";
if (/Snapchat/i.test(ua)) return "snapchat";
if (/LinkedInApp/i.test(ua)) return "linkedin";
if (/Pinterest/i.test(ua)) return "pinterest";
if (/\bTwitter\b/i.test(ua)) return "twitter";
if (/\bLine\//i.test(ua)) return "line";
return null; // ordinary browser, as far as the string admits
}
Two details that bite in production.
Use word boundaries on the short tokens. A bare test for Line matches half the strings on the internet, and Twitter without boundaries matches other clients too.
Test in order of traffic, not alphabetically. Instagram and Facebook cover most of what you will see, and every regular expression you run before them is wasted work on the majority of visits.
When the user agent stops telling the truth
Since iOS 26, some Facebook builds render their WebView with no FBAV or FBAN token at all. The string looks like plain Safari. Detection based purely on the user agent classifies those visits as a real browser, sends them the plain store link, and they die on the blank page.
There is no replacement marker. What is left is circumstantial evidence, and it has to be combined rather than trusted individually:
function looksLikeWebView() {
var ua = navigator.userAgent;
var isIOS = /iPhone|iPad|iPod/.test(ua);
if (!isIOS) return false;
var signals = 0;
// Safari sets this property, WebViews usually leave it undefined
if (typeof navigator.standalone === "undefined") signals++;
// in-app browsers keep their own chrome, so the visual viewport
// is shorter than Safari's would be on the same device
if (window.visualViewport &&
window.visualViewport.height < screen.height * 0.72) signals++;
// Safari on iOS reports a version token, many WebViews do not
if (!/Version\/[\d.]+ Mobile.*Safari/.test(ua)) signals++;
return signals >= 2;
}
None of these is conclusive on its own. A visitor with a browser toolbar and a keyboard open trips the viewport check in Safari too. Two signals together is the threshold we use, and it errs toward treating an unknown iOS visit as a WebView.
Erring in that direction is the right trade. Running the escape routine in a real browser costs nothing: the first hop fails silently and the plain store link opens. Skipping it in a WebView costs the visit.
What to do once you have detected it
Detection is the easy half. The escape differs per platform, and only Instagram ships something that works cleanly:
- Instagram:
instagram://extbrowser/?url=hands the URL to the system browser - Facebook and TikTok: no working scheme, so try
itms-appss://and fall back to a visible button quickly - Android, anywhere: an
intent://URL withS.browser_fallback_url
Measured on our own traffic, that leaves 1.2% of Instagram taps and 21.6% of Facebook taps needing the manual button. The full routing write-up has the code and the numbers.
Testing without guessing
The one thing that does not work is testing on your own phone by typing the URL, because that always opens in Safari and always succeeds. Post the link in a story visible only to you, or send it to yourself in a DM, and tap it from inside the app.
For the ambiguous cases, log the raw user agent along with whether the escape succeeded. After a few hundred taps the unmarked builds show up as a cluster of Safari-shaped strings with a failure rate no real browser would have.
If you would rather not maintain this across app updates, our links carry the detection and the routing, and report which taps escaped.