The intent chain runs out. Every package you tried came back empty, and now there’s a visitor without the app standing on a route that has nowhere left to send them except the store — or your own web page instead. Get that last step wrong and it’s indistinguishable from every other kind of broken: the visitor bounces, and nothing in your funnel tells you whether the app genuinely isn’t installed, whether your store link is malformed, or whether it’s pointing at a listing that doesn’t exist anymore — the same diagnostic fog as a postback that never shows up.
There are three separate ways to get this wrong, and none of them looks like an error until you go looking for it.
The two shapes
Android and iOS store fallbacks aren’t the same kind of value wearing different syntax. One is derived from something you already have; the other has to be looked up and stored on its own.
| Platform | Store fallback URL |
|---|---|
| Android | https://play.google.com/store/apps/details?id=<package> |
| iOS | https://apps.apple.com/app/id<numeric id> |
Android’s fallback is deterministic. It’s the exact same package string you already used to build the intent:// chain, restated as a query parameter — if you know com.instagram.android well enough to target it, you already know its Play Store URL too, with no separate lookup step.
iOS gives you no such shortcut. The numeric id is a separate, opaque value the App Store assigns per listing, and it cannot be derived from the app’s name, its bundle identifier, or anything else you’re likely to already have on hand. Numeric App Store ids cannot be guessed. You look each one up once, by hand, and store it next to the package name — and unlike the package name, there’s no fallback logic that recovers it for you if the lookup was never done.
That asymmetry is easy to miss while you’re building the Android side of a chain, because the Android fallback comes along for free the moment you’ve already picked a package to target. The iOS fallback doesn’t come along for free with anything. It’s a second, independent piece of data collection per app, and skipping it doesn’t fail loudly — it just means the iOS fallback silently isn’t there when the chain eventually needs it.
When a missing id means gone, not unknown
An empty iOS id field usually just means nobody has looked it up yet. Occasionally it means something different: there’s no id to find, because the listing itself no longer exists.
Microsoft retired consumer Skype in May 2025. The app is still installable on Android — its Play Store listing and package name are unaffected — but there is no App Store listing left to link an id to on iOS. That’s not a temporary gap waiting on a developer to file the right numeric value. It’s a permanent state for that one platform, while the Android side of the same fallback chain stays exactly as valid as it always was.
The distinction matters operationally. A missing Android package usually means “look it up,” and the fix is to go find the string. A missing iOS id can mean the same thing, or it can mean “this listing is gone and won’t come back,” and treating the second case like the first — leaving it as a TODO to revisit next sprint — just means the same dead lookup gets attempted again later. Once you’ve confirmed a listing is retired rather than merely unrecorded, drop the iOS fallback for that app entirely and let the chain end at your own web page on iOS specifically, while leaving the Android entry untouched, since Android was never affected by whatever took the iOS listing down.
Shopee: the case where no fallback beats a fallback
Most apps have one global store listing per platform, which is exactly what makes the two-shape table above work as a template. Shopee doesn’t. It ships a separate Android package per country — com.shopee. followed by a country code for each of eleven markets, from Indonesia and Brazil to Chile and Argentina — because it operates as eleven distinct national storefronts rather than one product with one listing.
That structure means there’s no single global Shopee page to fall back to. Pick any one country’s listing as “the” Shopee store fallback and you’ve picked correctly for exactly one of those eleven markets — and sent the other ten visitors to a listing in the wrong language, the wrong currency, and quite possibly not even installable in their region.
This is the judgment call worth making deliberately rather than by default: for Shopee, no store fallback beats a wrong one. A visitor who lands on your own web page after a failed app-open at least lands somewhere correct, regardless of which of the eleven markets they’re in. A visitor routed to the wrong country’s App Store or Play Store listing can’t tell “the app isn’t installed” from “someone routed me to the wrong country,” and a foreign-language store page reads as broken in a way your own web page doesn’t. Skip the store step for Shopee specifically, and let the chain end where it’s actually correct everywhere: your page, not a guess at which of eleven storefronts the visitor belongs to.
The same eleven-package structure that forces you to try com.shopee.id, com.shopee.br, com.shopee.vn and the rest one at a time on Android is exactly what makes a single store fallback impossible on either platform. There’s no single Shopee to fall back to, because there’s no single Shopee — there are eleven, and the fallback question only has a clean answer for apps where that isn’t true.
Where the fallback actually belongs
The store fallback isn’t just a URL choice — it’s also a question of timing, and getting the timing wrong silently breaks the candidates that come before it.
S.browser_fallback_url on an intent:// link accepts only http and https values; a market:// URL placed there is simply dropped by Android. And Chrome requires an actual user gesture to fire an intent:// navigation at all — without one, Chrome skips the app entirely and navigates straight to S.browser_fallback_url — the same silent landing in the browser that shows up anywhere a user gesture goes missing — which looks, from outside the page, identical to “the app isn’t installed.”
Put those two behaviors together and the consequence is easy to miss until it costs you a candidate. If you attach a fallback URL to the first package in a multi-package chain, a visitor who’s simply missing that first package — but has the second one installed — gets pushed into the browser before the second candidate is ever tried. The fallback fires too early and eats a conversion that should have worked. That holds for every step in the chain, including the last one: no candidate carries its own fallback URL.
The fallback belongs after every candidate is spent, not wired into any single step, and it’s your own page — not the intent chain — that decides when that point has been reached. Try every package in order first; only once the last one has failed do you send the visitor to a store link, and only if that store link actually exists and points at the right country.
Before you ship the fallback
| Check | Pass |
|---|---|
No candidate in the intent chain carries its own browser_fallback_url, including the last one | ☐ |
| Every iOS numeric id was looked up and stored per app, never derived or guessed | ☐ |
| A retired iOS listing is removed from the chain specifically, with the Android entry left untouched | ☐ |
| Apps without one global store page skip the store fallback entirely rather than picking one market | ☐ |
| The store link only fires after every package candidate has been tried and failed | ☐ |
FAQ
Can I just put market:// in S.browser_fallback_url and let Android sort it out?
No. S.browser_fallback_url only accepts http and https values — a market:// URL placed there is dropped, not converted, so the parameter behaves as if you never set it at all.
What happens if the visitor taps the link without triggering a real user gesture?
Chrome requires a user gesture to fire an intent:// navigation. Without one, Chrome doesn’t attempt to open the app at all — it goes straight to S.browser_fallback_url, and from outside the page that’s indistinguishable from the app genuinely not being installed.
Is a missing App Store id ever safe to treat as “not looked up yet”?
Only until you’ve checked. Treat it as unknown by default, but confirm it before you build automation around retrying the lookup — some ids are missing because the listing itself is gone, and a retired app has nothing waiting to be found.
Where the rule lives
Keep the ordering rule in one place rather than repeating it inside every intent chain you build: try every package candidate first, with no fallback URL on any of them, and only hand the visitor to a store link — or to nothing at all, for an app like Shopee — once the full list is spent. DarkCore’s Streams apply that ordering by default, per platform, so a retired iOS listing or a country-specific app without a global store page is a routing rule you set once rather than a failure mode you find in production.