Short answer: Android verifies each host in your intent filters. On Android 11 and lower, Google says "verification fails if even one host cannot be verified", so one staging or VPN-only host breaks App Links for all of them. On Android 12 and later, hosts verify independently, so only the failing host opens the browser. Publish the file on every host or remove the extra host.
What is happening
Per-host verification: Android's check of https://<host>/.well-known/assetlinks.json for every host declared in App Link intent filters.
The rules changed with Android 12:
- Android 11 and lower: the app becomes the default handler "only if it finds a matching Digital Asset Links file for all hosts in the manifest".
- Android 12 and later: the system "attempts to verify each one. If any host is verified, the app becomes the default handler for links from that verified host."
So the same manifest can break everything on older devices and only one subdomain on newer ones.
Why does this happen?
Manifests collect hosts over time: staging domains, marketing subdomains, regional sites. Any host without a reachable, valid file fails.
- Staging or VPN-only hosts: Google's servers cannot reach them.
- Subdomains without a file: Google treats non-wildcard subdomains as "unique, separate hosts".
- Wildcard hosts: for
*.example.com, the file must be at the root hostname,example.com. - Redirects on one host: for example
wwwserves the file but the apex redirects.
How to confirm it
- List every
android:hostvalue in your intent filters. - For each, run
curl -i https://<host>/.well-known/assetlinks.jsonand check for a 200,application/jsonand no redirect. - On an Android 12 or later device, run
adb shell pm get-app-links com.example.app. Each domain is listed with its own state; onlyverifiedis a pass. - Check each host with
https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://<host>&relation=delegate_permission/common.handle_all_urls.
How to fix it
- Publish assetlinks.json on every host the manifest declares.
- Remove staging, internal and VPN-only hosts from release builds, so shipped apps only declare hosts that serve the file.
- For wildcard hosts, serve the file at the root hostname.
- Remove redirects on
/.well-known/assetlinks.jsonfor every host. - Reinstall and re-verify:
adb shell pm set-app-links --package com.example.app 0 alladb shell pm verify-app-links --re-verify com.example.app- wait a few minutes, then
adb shell pm get-app-links com.example.app
<!-- Release manifest: only hosts that serve assetlinks.json -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="example.com" />
<data android:host="www.example.com" />
</intent-filter>
Check each host's file with curl -I https://<host>/.well-known/assetlinks.json and Google's Statement List tester, then read the per-host state with adb shell pm get-app-links com.example.app. For delivery problems on one host, see assetlinks redirect.