Free tier, no card requiredDynamic QR codes that update after printGDPR-compliant scan analyticsBuilt for agencies, freelancers & in-house teamsFree tier, no card requiredDynamic QR codes that update after printGDPR-compliant scan analyticsBuilt for agencies, freelancers & in-house teamsFree tier, no card requiredDynamic QR codes that update after printGDPR-compliant scan analyticsBuilt for agencies, freelancers & in-house teamsFree tier, no card requiredDynamic QR codes that update after printGDPR-compliant scan analyticsBuilt for agencies, freelancers & in-house teams
All posts
A QR code branching into two outcomes: a verified app opening directly, and a browser window opening instead.
Explainer

Can a QR code open an app directly? Universal Links, App Links, and why most scans still land in a browser

Why a QR scan often opens a browser instead of an already-installed app. How Universal Links, App Links, assetlinks.json and deferred deep linking actually work, and where the chain breaks.

ScanKit

ScanKit · Organization

· 19 min read

Can a QR code open an app directly? Universal Links, App Links, and why most scans still land in a browser

An agency runs a loyalty campaign for a retail client. The app is already on the customer's phone, points are sitting in it, and the QR code on the receipt is meant to open the app straight to the "scan to earn" screen. Instead, the phone opens Safari or Chrome, shows a plain web page, and the whole point of the campaign, keeping the interaction inside the app the client paid to build, evaporates in one scan. This is not a broken QR code. It is a mismatch between what a QR code actually does (encode a URL) and what "opening an app" requires (a chain of OS-level trust and resolution that a camera scan does not automatically join). This article covers the mechanism properly: what Universal Links and App Links are, why they are not guaranteed to fire just because someone scanned rather than tapped, what deferred deep linking adds for people who do not have the app yet, and the concrete list of things that break the chain, so an agency can diagnose and fix a "why does this land in the browser" ticket instead of guessing. This is a different problem from routing new installs to the right app store, which is covered in one QR code for both app stores; this article is about people who already have the app installed, or who need the store visit and the app's first screen to be seamlessly connected.

What "deep linking" actually means, and the three mechanisms behind it

A deep link is a URL that, instead of opening a generic homescreen, takes the user to a specific piece of content inside an app: a product page, a loyalty balance, an event ticket. Three mechanisms exist to make that happen, and they are not interchangeable.

Universal Links are ordinary https:// URLs. There is no special scheme; the trick is a small JSON file, the apple-app-site-association (AASA) file, hosted at /.well-known/apple-app-site-association on the destination domain, paired with an entitlement declared inside the app itself. Apple's own framing is that "your app adopts an entitlement... and your web server adopts a single JSON file." Because only the domain's owner can publish that file, no other app can claim to handle links for a domain it does not control, which is the specific weakness Universal Links were built to close.

Since iOS 14, devices do not fetch the AASA file directly from your server on every check. They fetch it through an Apple-managed content delivery network, with a separate "alternate mode" available for internal or development domains that should bypass that cache. That detail matters operationally: a fresh AASA file does not necessarily take effect the moment it is uploaded, because the CDN has to catch up.

iOS also remembers a user's prior choice. If someone has previously chosen to view a domain's content in Safari rather than the associated app (by long-pressing a link or backing out of the app), the system leans toward repeating that choice for the same domain until the user deliberately re-engages the app, typically by tapping a Smart App Banner again.

A Smart App Banner is the strip that appears at the top of Safari, declared with one meta tag in the page's head (apple-itunes-app). If the app is installed, tapping the banner opens it directly; if not, it routes to the App Store listing. It only fires when Safari itself renders the tagged page and a person interacts with the banner, not inside an in-app browser like the ones Instagram or TikTok open, and not automatically on scan. It is a related but separate piece of the same puzzle, and worth knowing about so it is not confused with true Universal Link resolution.

Android's equivalent is App Links, and the word that matters is "verified." An intent filter in the app declares android:autoVerify="true", which (from Android 6.0 / API 23 onward) triggers Android to check, at install time, whether the app is actually authorised to handle links for the hosts named in that filter. The check works by fetching a Digital Asset Links file at https://yourdomain.com/.well-known/assetlinks.json, a manifest that "publicly declares which apps are authorized to handle links for your domain." Only once that file resolves correctly does Android treat the app as the verified handler for that domain, rather than presenting the user with a disambiguation dialog or simply opening a browser.

Custom URL schemes: the older, weaker option

Before Universal Links and App Links existed, apps registered their own scheme, something like myapp://. The scheme approach has two structural problems that neither platform has ever fully solved for it. First, there is no reliable way to detect in advance whether the target app is installed. If it is not, tapping the link produces nothing (a silent dead end since iOS 9.2, with no built-in fallback to a website or store listing). Second, because any app could historically register any scheme string, two different apps could claim the same one, with no proof of ownership involved, which is precisely the ambiguity Universal Links and App Links were designed to remove by tying the link to a verified web domain instead. Custom schemes still show up as a fallback layer inside some attribution SDKs, but they are not the primary mechanism an agency should rely on in 2026.

Why a QR scan does not behave like a normal tap

This is the part that catches agencies out, and it deserves precision rather than a shrug. Universal Links and App Links are designed to activate when the operating system itself resolves a tap on a qualifying HTTPS link. A QR code scan does not automatically go through that path. What happens depends on which app performed the scan and what it did with the decoded URL.

On a developer forum, someone asked the exact question an agency should ask before a campaign: does a QR code encoding a Universal Link actually invoke the app when scanned by the Camera app? Their own answer, after testing, was that it worked for their app and domain, but neither Apple nor anyone official confirmed the underlying mechanism. That is the honest state of this feature: it is well corroborated in practice, but not a documented guarantee from Apple or Google. Three variables decide the outcome in most reported cases.

Which app performed the scan. The native Camera app on iOS and the native scanner on Android (often via Google Lens) generally hand a decoded HTTPS URL to the OS the way a tapped link would be handled, which is why Universal Links and App Links often do resolve correctly through them. A third-party scanner app is not guaranteed to do the same; some open the URL inside their own in-app browser or webview instead of handing it to the system, and a webview does not carry the same link-resolution privileges as a genuine OS-level tap.

Which browser is set as default. Deep-link vendors document, as a known and sometimes unresolved issue, that certain fallback behaviours on iOS work reliably only in Safari and break down in Chrome or other third-party browsers on the same device. If a person's default browser is not Safari, a link that would resolve correctly for one user can silently fall through to a plain web page for another, on identical hardware.

Tapped versus pasted or typed. Deep-link platforms explicitly warn against testing links by pasting them into Notes or typing them directly into a browser's address bar, because the operating system deliberately does not treat that as the same trust context as a tap, and will not open the app. A QR scan sits closer to a tap than to a paste in most cases, but the exact internal handling is not published, which is why testing on real devices with the real scanner your campaign expects (native camera, not a QR reader app) matters more here than for most features.

The practical upshot: correctly configured AASA and assetlinks.json files are necessary but not sufficient. An agency that gets a "why doesn't this open the app" ticket should first ask which app the customer scanned with and which browser is set as default, before assuming the campaign's technical setup is broken.

Deferred deep linking: routing people who don't have the app yet

Universal Links and App Links only help once the app is already installed. A separate problem, one with no native OS solution, is getting someone who does not have the app to install it and then land on the exact content they scanned for, rather than a generic home screen. That gap is called deferred deep linking, and closing it requires either a third-party attribution SDK (Branch, AppsFlyer, Adjust are the common names) or building the equivalent yourself.

The mechanism these SDKs use is a form of probabilistic matching, sometimes called device snapshotting. At the moment someone clicks the QR-derived link, the vendor's server records a "browser snapshot": signals like IP address, OS, OS version, device model and screen size, timestamped. After the app store install completes and the app opens for the first time, the SDK takes a "device snapshot" of the same kind of signals and tries to match it back to a recent browser snapshot from the same device. Branch, one of the larger vendors in this space, describes having hundreds of millions of such snapshots backing the matching. It is a reasonable engineering approach to a genuinely hard problem, but it is probabilistic, not deterministic. It cannot cite a shared identifier the way a login can; it is inferring "probably the same device" from a cluster of ordinary signals.

That inference sits close to a policy line worth knowing about before an agency builds a campaign around it. Apple's App Store Review Guidelines prohibit deriving data from a device specifically to identify it, described in the guidelines as fingerprinting, regardless of whether the user has granted tracking permission under App Tracking Transparency. Independent academic research has found real-world evidence of apps computing shared, fingerprint-derived identifiers precisely to route around ATT restrictions, which is part of why the rule exists in its current explicit form. None of this means deferred deep linking via a reputable SDK is against the rules; established vendors design their matching to stay inside Apple's policy and generally avoid claiming a deterministic match. It does mean an agency should treat vendor-reported match-rate confidence as a best-effort estimate rather than a guarantee, and should have a documented reason (in the client's privacy notice and DPA) for using a third-party attribution SDK at all, particularly for campaigns aimed at EU users. See are QR codes GDPR-compliant for the broader consent and data-collection picture a scan can trigger.

Where the chain actually breaks

Most "the app didn't open" reports trace back to one of a short list of causes, in roughly descending order of how often agencies hit them in practice.

  • Verification never completed on Android. The assetlinks.json file is missing, malformed, or hosted at the wrong path, or the intent filter never declared autoVerify="true". Android silently falls back to opening a browser rather than surfacing an error, so this can go unnoticed for an entire campaign.
  • The AASA file is served through a redirect. Apple's tooling explicitly calls out HTTP redirects on the AASA response as unsupported; the file has to be served directly, with the correct content type, as valid JSON, with no file extension, at exactly /.well-known/apple-app-site-association.
  • CDN propagation lag on iOS. Because devices fetch the AASA file through Apple's CDN rather than your server directly since iOS 14, a freshly corrected file does not take effect instantly everywhere; a "fixed it, still broken" report an hour later may just be cache catching up.
  • The user's prior in-browser choice is sticking. If someone previously chose to keep viewing a domain in Safari, iOS can keep honouring that choice on later visits until the user explicitly re-engages the app.
  • The QR code points at a generic shortener domain, not the client's own domain. Universal Links and App Links are verified per domain. If the encoded URL is on a third-party short-link domain that has no AASA or assetlinks.json of its own, and no relationship to the client's app, there is nothing for either OS to verify, and the visit resolves as an ordinary web request every time, regardless of how well the client's own domain is configured.
  • The default browser is not Safari, or the scan came from a third-party scanner app. As above: these change which trust context the tap lands in, and are worth ruling out before assuming a configuration fault.

Setting a QR code up to give deep linking its best shot

None of the mechanisms above live inside the QR code. A QR code only ever encodes a URL; every behaviour described here comes from what sits behind that URL and how the app is configured. That has two practical consequences for how an agency should build the campaign.

Point the code at a domain the client actually owns and controls, ideally the same domain the app's AASA and assetlinks.json files are already configured for, rather than a generic shortener. This is the single most common reason deep linking silently fails on an otherwise correctly configured app: the QR code never gave either OS a domain it could verify.

Use a dynamic QR code so the destination URL can be corrected without reprinting if verification turns out to be misconfigured after the print run has already shipped, covered in dynamic vs static QR codes. Discovering an assetlinks.json typo after ten thousand receipts have printed is a much smaller problem when the fix is a redirect update rather than a reprint.

Decide honestly whether the campaign needs deferred deep linking at all. If the goal is simply getting a new user to the right app store listing, plain device-based redirection (reading the User-Agent header and sending iOS to the App Store, Android to Google Play) is simpler, has no fingerprinting exposure, and is the approach covered in one QR code for both app stores. Reach for a full deferred deep linking SDK only when the campaign genuinely depends on landing a brand-new install directly on a specific piece of content (a particular loyalty offer, a specific event page) rather than just the app's home screen, since that is the only scenario the added complexity, cost and privacy exposure actually buys something.

Test with the real scanning path the campaign will see in the wild, meaning the native Camera app on a physical device, not a QR reader app on a desk, and not a pasted link. Given how much of this mechanism depends on which app performs the scan, testing with anything else risks confirming a setup that fails for a meaningful share of real customers.

Flow diagram showing a QR scan resolving into either a direct app open or a browser fallback.
The path from scan to result: 1) the QR code is scanned, 2) which app or browser handles the tap, 3) whether verified domain files (AASA/assetlinks.json) are present, 4) the app opens directly, or 5) a browser opens instead.

Where this shows up in real campaigns

Retail packaging loyalty enrolment. A QR code on packaging routes an existing customer straight into the loyalty section of an app they already have, and a first-time scanner into store-listing-then-enrolment instead.

Event badges and booth QR codes. An attendee with the event app already installed gets routed to their session schedule or a specific sponsor's booth page rather than a generic event website, while a first-time attendee lands in the store first.

Restaurant loyalty on receipts. A QR code printed on a receipt sends an existing app user straight to a points-earning confirmation screen for that visit, which only works if the receipt's QR code points at the same verified domain the app's deep-linking configuration already trusts.

Out-of-home promo codes. A poster or transit ad QR code aimed at driving people into a specific promotional screen inside an app depends most heavily on deferred deep linking, since the audience is largely people encountering the app for the first time from a public, unattributed placement, which is exactly the case third-party attribution SDKs like Branch and AppsFlyer are built around.

Frequently asked questions

Can a QR code open an app directly instead of a browser?

Yes, if the app is already installed and the destination domain is correctly configured with an apple-app-site-association file (iOS) or a verified assetlinks.json file (Android). The QR code itself has no logic; it only encodes a URL, and everything about whether that URL opens the app or a browser is decided by the OS-level configuration behind it and, less predictably, by which scanner app and browser the person used.

The most common causes are: the scan came from a third-party scanner app or in-app browser rather than the native Camera app, the person's default browser is not Safari, the destination URL was on a generic shortener domain rather than the client's own verified domain, or iOS is honouring a prior "always open in Safari" choice for that domain. Verification files being correctly configured is necessary but not, by itself, sufficient.

A deep link is the general concept: a URL that opens specific content inside an app rather than its home screen. A Universal Link is Apple's specific, verified implementation of that concept for iOS, using ordinary HTTPS URLs plus an apple-app-site-association file. Android's equivalent is called an App Link. Custom URL schemes are an older, unverified way of building the same general idea, with weaker guarantees.

Do I need a service like Branch or AppsFlyer to make QR code deep linking work?

Not if everyone scanning already has the app installed and your own domain is correctly configured with Universal Links and App Links; the native OS mechanisms handle that case without a third-party SDK. A dedicated attribution SDK becomes worth its cost specifically for deferred deep linking, meaning routing a brand-new install straight to specific content rather than the app's home screen, which has no native OS equivalent.

The matching techniques these SDKs use are probabilistic (based on signals like IP address, device model and OS version, not a shared login identifier), and Apple's App Store guidelines explicitly prohibit using such signals to fingerprint a device for identification purposes, regardless of App Tracking Transparency permission status. Reputable vendors design their matching to stay within that policy, but an agency deploying one should document the legal basis for it in the client's privacy notice, particularly for EU audiences, rather than treating it as automatically consent-free.

Yes. Both platforms check the same destination domain independently, iOS via the apple-app-site-association file, Android via assetlinks.json, so a single QR code and a single destination URL can trigger correct deep-link behaviour on both platforms as long as both verification files are correctly hosted on that domain.

Why does the same QR code work for some customers and not others?

Because the outcome depends on variables specific to each customer's phone, not just the campaign's configuration: which app they used to scan, which browser is set as default, whether they have previously chosen to view that domain in a browser rather than the app, and whether the OS's link verification has propagated on their device. Two people scanning the identical code on the same day can get different results.

How do I test whether my QR code will open the app correctly?

Test on a physical device using the native Camera app, since that is closest to how most real customers will scan, rather than a desktop QR reader or a pasted link, which operating systems deliberately treat differently from a tap. Test on both a device where the app is already installed and one where it is not, and check the result in both Safari-default and non-Safari-default configurations on iOS.

The short version

A QR code only ever encodes a URL; whether that URL opens an app directly or lands in a browser is decided by verification files (apple-app-site-association on iOS, assetlinks.json on Android) hosted on the destination domain, by whether the OS treats the scan as a genuine tap, and, for people who don't have the app yet, by whether a deferred deep linking SDK is doing probabilistic matching behind the scenes. Point the QR code at a domain the client actually controls, not a generic shortener, use a dynamic QR code so a misconfigured verification file can be fixed without reprinting, and test with the real Camera app on a real device before the print run ships. If the goal is only getting new users to the right app store, the simpler device-based redirection covered in one QR code for both app stores will do the job without the added complexity of deferred deep linking at all.

Share

Keep reading