deep-linking

One Link for Both App Stores: How to Route iOS and Android Users

One link for both app stores: the four ways to route iOS and Android users from a single URL, what each one breaks, and why the redirect should be yours.

WarpLink Team··21 min read

TL;DR: One link for both app stores means a single URL that decides, at the moment of the tap, whether this visitor gets the App Store, Google Play, your app, or your website. There are four ways to build it: a landing page with two buttons, a user-agent redirect you host, a short-link service, or nothing beyond the fallbacks iOS and Android already provide. They differ less in what they do than in what they break: a two-button page spends a tap at the highest-intent moment in the funnel, the operating-system fallbacks never route anyone to a store, and any URL pointing straight at a store strands desktop visitors and forgets where the person was heading. The routing itself is three decisions in order: which platform, then whether the app is already installed, then which fallback applies when it is not. Only a redirect you control answers the third one and records the tap while doing it, which is what later lets a new install be matched back to the link that caused it. Android can make that match exactly, through the referrer the Play Store carries through an install, and iOS cannot, so it matches probabilistically inside a short window.

Your app lives at two URLs, and every surface that promotes it gives you one slot: one link in a profile bio, one URL under a video, one code on a poster. So the shared URL has to serve four people at once: someone on an iPhone without the app, someone on an Android phone without the app, someone on either who already has it and should never see a store, and someone at a desk or inside an embedded browser, where neither store is useful yet.

That one URL is what people usually mean by a smart app link, or a single download link for iPhone and Android. There are four common ways to build it, and they are not four brands of the same thing: each draws its ceiling somewhere different.

ApproachWho runs itOpens the installed appPicks the right storeKeeps the destination through an install
A landing page with two buttonsYouNoThe visitor doesNo
A user-agent redirect you hostYouOnly if the domain is one your app claimsYesNot without a matching service behind it
A short-link serviceSomeone elseDepends on the serviceYesOnly if it records the click and matches installs
Operating-system fallbacks onlyNobodyYesNoNo

Each gets a section, then the four things that break whichever you pick.

Option 1: A Landing Page With Two Buttons

The first answer most teams reach for is a page on their own site with an App Store badge and a Google Play badge side by side. It is honest, it takes an hour, it works everywhere, and it is the only option here that needs no detection logic, because the visitor does the detecting.

What it costs is a tap and a decision at the moment when neither is free. Someone tapping a link from a video description has already decided to try the app, and the two-button page interrupts that with a small quiz. A fraction pick the wrong badge, land on a listing for a phone they do not own, and quietly stop. Two smaller problems follow from the same shape: the page does nothing for people who already have the app, who read about it in a browser instead of opening it, and it loses the destination, because the badges point at a store listing rather than at the product or shared item the link was about.

Use it as the destination for desktop traffic. Do not use it as the answer for the phone in someone's hand.

Option 2: A User-Agent Redirect You Host

The second answer is a small server that reads the User-Agent header, decides which platform is asking, and answers with a 302 to the matching store. No page, no tap, no decision. The DIY section below writes it out in full.

What is not in the code is the part that decides whether the same URL can also serve people who already have the app. Universal Links on iOS and App Links on Android are resolved by the operating system, from the hostname in the URL, before any HTTP request is made: iOS checks the apple-app-site-association file it fetched for that domain, Android checks assetlinks.json. If the domain is one your app claims and the path matches, the system opens your app and your server never hears about the tap.

The useful consequence is that a redirect on a domain your app claims covers both audiences with no branching. Installed users are intercepted and land in the app, everyone else falls through to your server, which routes them to a store. You never detect the installed state, which is good, because there is no way to detect it.

The uncomfortable consequence is that a redirect cannot open your app. A Universal Link resolves on a user-initiated navigation, not on a Location header that arrives afterwards, and Android applies the same rule to app launches with no user gesture behind them. A 302 from a domain your app does not claim to a domain it does is a dead end, because the hop that has to be intercepted is the one the person tapped. Deep Links in Instagram and Facebook In-App Browsers covers what that rule does inside embedded browsers.

So the domain is not a detail: a redirect on a domain your app claims is a store router and a deep link, and on any other domain it is only ever a store router.

The third answer is to let something else run option 2 for you. These come in two shapes that look identical from the outside and are not the same thing.

A general-purpose shortener turns a long URL into a short one, and the better ones let you set a different destination per platform. That covers store routing and nothing past it. The link cannot open your app, because that requires association files served from the link's domain naming your bundle identifier and package name, and a service that does not know your app cannot serve them.

Link infrastructure is built for apps, so it serves those association files, routes per platform, records every tap, and joins a later install back to the tap that caused it. Enterprise mobile measurement partners sell the same capability inside a much larger suite, priced and sold accordingly.

Four questions separate them, whichever shape you are looking at.

  1. Can it serve association files for your app on the link's domain? If not, the link can never open the installed app, and you are buying option 2 with a dashboard.
  2. Whose domain is on the link? A shared domain that gets filtered somewhere takes your links down with it.
  3. Does it record the click in a form you can join to an install? Counting clicks is easy; telling you which channel produced a paying user is the feature that changes decisions.
  4. What happens to your printed codes if it shuts down? When a free link service closes, every code pointing at it dies at once. A custom domain is the insurance: the codes name a hostname you own.

Option 4: The Fallbacks iOS and Android Already Give You

The fourth answer is to share a URL on your own website and let the operating systems route it. For installed users this works well: a verified Universal Link or App Link on your domain opens the app directly, and this is the only option here where the app opens with nobody hosting a redirect.

For everyone else the platforms offer a banner rather than a redirect. On iOS, Safari renders a Smart App Banner when the page includes a meta tag naming your App Store identifier:

The banner sits at the top of the page, reads "View" when the app is missing and "Open" when it is installed, and hands the app-argument URL to the app on that Open tap. On Android, Chrome can show a native app install prompt when your web app manifest lists the app under related_applications with prefer_related_applications set to true, though Chrome decides when to show it using its own engagement heuristics.

The limits are structural on both sides.

  • The banner is not routing. Nobody is redirected anywhere, so a visitor who ignores it is simply on your website, which for an install campaign is the wrong outcome for most taps.
  • The iOS banner is Safari's. It does not render in Chrome for iOS, nor inside the embedded browsers social apps use, which is where a large share of shared links are opened.
  • The same-domain rule applies. iOS does not fire a Universal Link when the user is already browsing that domain, so a link from one page of your site to another opens as an ordinary page. A separate link hostname is the way around that, which pushes you back toward options 2 and 3.
  • Nothing survives an install. The banner sends the visitor to the store, and the store returns them to a home screen with no idea why they came.

Treat both as a floor under whatever else you do.

What Each One Breaks

Four things break in this space, more or less independently of which option you picked.

Desktop. A store URL on a desktop is a dead end wearing a store's clothes: the App Store page for a phone app renders with no way to install anything from that machine, and a Play listing only does better if the visitor is signed in and willing to push an install to a device they own. Desktop is not a rounding error either: links in newsletters, docs, and community posts get opened at desks constantly.

Tablets. Safari on iPadOS asks for desktop sites by default, so an iPad's request carries a Macintosh user agent and a regex looking for iphone|ipad|ipod does not match it. No header separates that request from an actual Mac with certainty. Android tablets are far less trouble, because they keep the Android token and route correctly by accident.

In-app browsers. A tap inside Instagram, TikTok, Facebook, or a mail client usually opens the page in an embedded browser the host app controls. Your redirect still runs and still picks the right store, so options 2 and 3 survive. What does not survive is the app handoff for people who already have your app, because the operating system is largely not in the loop inside someone else's webview. The fix differs per app, and is worked through in Deep Links in Instagram and Facebook In-App Browsers.

Deferred context. This one survives no option here by default. When a visitor without the app reaches a store listing, installs, and opens the app, the URL they tapped is gone, because neither store hands a URL to an app it just installed. That is a boundary in the platforms rather than a bug in your routing, and crossing it needs a server that remembers the tap.

The Routing Table

Here is the whole decision, in order. Read platform first, then installed state, then the fallback.

VisitorApp installedWhere the tap should endWhat decides it
iOS, system browserYesThe exact screen in your appapple-app-site-association, matched before any request
iOS, system browserNoThe App Store, destination held for first launchYour redirect: the link's iOS fallback URL, else the app's App Store URL
iPadOS in desktop modeEitherThe same as iOS, when detection catches itA parser heuristic for an iPad reporting a desktop string
Android, browser or Custom Tab (Chrome's in-app browser)YesThe exact screen in your appassetlinks.json, verified at install time
Android, browser or Custom TabNoThe Play listing, with a referrer naming the linkYour redirect: the link's Android fallback URL, else the app's Play Store URL
Android, installed but unverifiedEitherThe browser, or a chooser on older targetsautoVerify never succeeded, so your app does not own the host
Either platform, embedded webviewEitherA page with a tap target for the app or the storeThe host webview intercepts the navigation, so no system handoff happens
DesktopNot applicableA web page you ownThe link's default destination
A preview crawlerNot applicableHTML with Open Graph and Twitter Card tagsThe user agent matched a known crawler pattern, so no redirect is issued

Three rows repay a second read. The "installed, system browser" rows say nothing about your server, because your server is not involved: the system resolved the hostname against a file it already had, which is why the domain on the link is the most consequential choice in the design. And the crawler row exists because a shared link is also fetched, repeatedly, by every service that renders a preview card, so redirecting those fetches to a store hands you an unfurl carrying the store's title and image. Open Graph HTML is how a shared link keeps its own preview.

The DIY Version in About Forty Lines

Here is option 2, complete, as a Node server. Save it as redirect.mjs, run node redirect.mjs, and point a hostname at it.

That is the whole mechanism, and for a disposable link it is enough. The two response headers are not decoration: without Cache-Control and Vary, some intermediaries treat a 302 as cacheable, which is how the Android answer ends up served to every iPhone behind the same proxy for an hour.

What the forty lines do not do is where the maintenance cost lives.

  • The iPad case. An iPad in desktop mode does not match IOS and falls through to the web fallback. Fixing that means a user-agent parser updated as device strings change, which is a dependency and a recurring upgrade rather than a regex.
  • No click record. No counting, no per-platform split, no per-channel comparison. Adding it means a datastore, a write path that does not slow the redirect, bot filtering so crawler traffic does not inflate the numbers, and something to read it back.
  • No association files. The redirect cannot open the installed app unless this hostname also serves apple-app-site-association and assetlinks.json for your app, with the right content type, no redirects in front of them, and a regeneration whenever your signing certificate changes.
  • No install referrer. The Play Store carries a referrer parameter through an install, but only when the URL you sent the visitor to was a play.google.com address that had one attached. This code sends a bare store URL, so Android's one deterministic attribution signal is discarded.
  • It has to stay up. Every printed code, bio link, and archived email depends on this process, and its failures are invisible to you and permanent for the person who tapped.

Build it when the link is disposable. When the question becomes which placement actually produces users, you have started building a product rather than a redirect.

Why the Redirect Has to Be One You Control

Everything so far treats the four options as roughly interchangeable for store routing, which they roughly are. What separates them is what happens after the store. Between the tap and the first launch of a freshly installed app every identifier is gone, so that new user is indistinguishable from someone who found you by searching the store directly.

Recovering the connection is called a deferred deep link, and it needs two things: a server that saw the tap and wrote down what it looked like, and code in the app that reports its first launch and asks. The server compares the two and answers with the link the person originally tapped. That is why the redirect has to be yours, or one doing this on your behalf. A store URL you shared directly has no server in front of it, and a shortener that only rewrites URLs kept nothing an install could be matched against.

The match then works differently per platform.

On Android it can be exact. When the redirect sends the visitor to a play.google.com URL carrying a referrer parameter, the Play Store stores that string against the install and hands it back to the app on first launch. So send Android visitors to a play.google.com address rather than through a chain that loses the referrer, because the exact match is free and giving it up is not.

On iOS there is no equivalent. The App Store passes nothing through, so a genuine first install is matched on signals visible at both ends: the address the request came from, the device language, the timezone. That match is probabilistic and its confidence decays as the gap between tap and launch grows, which is why the window a server looks back over is measured in hours rather than days.

So read the confidence on every result rather than treating a match as a fact. A deterministic match is flagged as guaranteed, and anything sensitive, such as signing someone in or granting a reward, belongs behind that flag rather than behind a score. Both paths are covered in How to Track App Installs From a Link and the deferred deep links concept doc.

QR Codes, Packaging, and Anything You Cannot Edit Later

Everything above is recoverable on a web page: you can change a link in a bio in ten seconds. Print is where the choice becomes permanent, and where pointing at a store hurts most. A code printed with an App Store URL inside it serves one platform for as long as that poster exists, half the people who scan it get the wrong store or a page they cannot install from, and there is no fixing it without reprinting.

Encode a short URL on a hostname you own and the destination behind it stays editable forever. The code is fixed, the routing is not. That is the argument for a dynamic code, and for a custom domain: the printed thing should name something you can still repoint in three years. Give every placement its own link so the lobby poster and the trade-show banner are separate rows rather than one blended number. Sizing, error correction, and the quiet zone are covered in QR Code Deep Linking.

Frequently Asked Questions

How do I make one link that opens the App Store on iPhone and Google Play on Android? Put a redirect in front of both store URLs and let it read the request: a 302 to the App Store for an iOS user agent, to Google Play for an Android one, and to a web page for everything else. What matters more than the detection is whose domain that redirect runs on, because a domain your app claims can also open the app for people who already have it. A domain you do not control never can.

Can one link open the app for people who already have it installed? Only when the link's domain is one your app claims through an association file. iOS reads apple-app-site-association and Android reads assetlinks.json from the host in the URL, so the handoff is decided by the hostname the person tapped rather than by wherever the redirect would have sent them. When the app is installed and the domain is verified the tap never reaches your server, and when it is not the request arrives and the redirect picks a store.

Where should a link like this send someone on a desktop computer? To a web page you own, never to a store listing. An App Store page opened on a desktop shows a phone app the desktop cannot install, and a Play listing at best offers a remote install to a device the person is signed in on. Send them to a page that explains the app and gives them a way to move to their phone, such as a code they can scan from the screen.

Do I need an SDK in my app just to route iOS and Android to different stores? No. Store routing happens on the server, before your app exists on the device, so a redirect alone sends each platform to its own listing. Code in the app is needed for the two jobs routing cannot do: opening a specific screen from a tapped URL, and learning on first launch which link brought a new user in.

Why does my redirect send iPad visitors to the website instead of the App Store? Because Safari on iPadOS asks for desktop sites by default, so the request arrives carrying a Macintosh user agent and a naive test for iPhone or iPad never matches. No request header separates an iPad in that mode from a Mac with certainty, which is why hand-written detection leaks iPads into the desktop branch. Keep a visible App Store button on whatever the desktop branch serves, so the misroute costs a tap rather than a visitor.

What happens to the link if I add a platform or change store URLs later? That depends entirely on what the shared URL points at. If it points straight at a store, nothing happens, because every copy of that URL is already printed, posted, and pasted. If it points at a redirect you control, the store URLs are configuration sitting behind it, so adding a platform or moving a listing is a settings change rather than a reprint.

Options 1 and 4 are things you can build this afternoon and probably should, as a floor. The gap between them and a link that earns its slot is a redirect that routes correctly, keeps its own record of the tap, and can answer a question from your app hours later. That is what WarpLink is: link infrastructure for mobile teams, doing all three from one link.

Deep linking. One short link routes per platform at the edge with sub-10ms redirects, using a maintained user-agent parser, and falls back in a defined cascade: the link's own fallback URL first, then the store URL on your app registration. Register an app and the apple-app-site-association and assetlinks.json files are generated and served for the link domain, including a custom domain of your own, so the same URL opens the installed app instead of routing it to a store.

Install attribution. Every tap that reaches the redirect is recorded before the store takes over, so a first launch can be matched back to it: deterministically on Android through the referrer carried on the Play Store URL, probabilistically on iOS inside a window that defaults to six hours per link.

Real-time analytics. Clicks, platforms, countries, referrers, and campaign values land as they happen, per link, which is what makes one link per placement worth the effort.

Create a free WarpLink account and get all three in one SDK, with 10,000 clicks a month, 100 links, and one custom domain free for three months on the free plan. The deep linking concept doc has the full routing and fallback cascade.

WarpLink Team

Building affordable, reliable link infrastructure for mobile teams. Deep linking, install attribution, and real-time analytics in one SDK.

Related Posts