Android App Links in 2026: why your https:// links still open in the browser
Digital Asset Links verification, the assetlinks.json gotchas, and the adb commands that tell you why Android won't hand a link to your app.
A custom URI scheme like myapp://item/42 is easy to wire up and easy to get wrong in a different way: any app can register the same scheme, so the OS can’t promise your app is the one that opens it. An Android App Link — a real https://yourdomain.com/... URL that opens your app directly instead of a browser tab — fixes that, but only if the OS can verify your app actually owns the domain. Most of the time a link like this quietly falls back to Chrome, and the manifest looks completely correct. The missing piece is almost always Digital Asset Links verification, not the intent filter.
The two things that have to agree
An App Link only auto-verifies when two independent pieces line up: the app’s intent filter, and a JSON file the domain itself serves. Neither side knowing about the other is the point — it’s what stops a random app from claiming your domain.
The intent filter goes in the manifest:
<activity android:name=".MainActivity" android:exported="true">
<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="https"
android:host="mfkapps.com"
android:pathPrefix="/apps" />
</intent-filter>
</activity>
android:autoVerify="true" is what triggers Android to actually check ownership at install time, rather than just matching the URL pattern. Skip it and the filter still matches links, but only after the user has explicitly chosen your app once from the disambiguation sheet — it never gets promoted to “always open automatically.”
The domain side is a static file at a fixed, non-negotiable path:
https://mfkapps.com/.well-known/assetlinks.json
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.mfk.granyn",
"sha256_cert_fingerprints": [
"14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A3:88:1E:22:0B:99:19:33:4B:C0:5B:0B:64:0C:1D"
]
}
}]
package_name and sha256_cert_fingerprints both have to match the exact build that’s installed. That fingerprint detail is where almost every broken setup actually breaks.
The fingerprint mismatch that breaks everything silently
sha256_cert_fingerprints needs the SHA-256 of the signing certificate for the APK the user has installed — and if the app ships through Play App Signing, that’s Google’s signing key, not the upload key you sign locally. Get the wrong one in assetlinks.json and verification fails with no error the user ever sees; the link just opens in a browser, forever, and nothing in Logcat points at the file.
Get the right fingerprint from the Play Console, not from your local keystore:
Play Console → Setup → App integrity → App signing key certificate → SHA-256
For a debug build during development, use the debug keystore’s fingerprint instead, and list both in the array if you want debug and release to verify against the same assetlinks.json:
keytool -list -v -keystore ~/.android/debug.keystore \
-alias androiddebugkey -storepass android -keypass android
sha256_cert_fingerprints accepts an array, so both fingerprints can sit side by side — verification checks whether any entry matches the installed app’s signature.
Verifying it actually worked
Android runs the verification check at install time and caches the result, so the fastest way to see the real status is asking the OS directly rather than guessing from behavior:
adb shell pm get-app-links com.mfk.granyn
A working setup reports the domain as verified:
com.mfk.granyn:
ID: d3a1f0b2-...
Signatures: [14:6D:E9:...]
Domain verification state:
mfkapps.com: verified
If it says legacy_failure or none, the fingerprint or JSON is wrong — fix assetlinks.json, then force a re-check without reinstalling:
adb shell pm verify-app-links --re-verify com.mfk.granyn
adb shell pm get-app-links com.mfk.granyn
To test the actual tap-through behavior once verification passes, fire an intent the way a browser or another app would:
adb shell am start -a android.intent.action.VIEW \
-d "https://mfkapps.com/apps/granyn"
If the app opens with no chooser sheet, verification is working. If a disambiguation dialog appears, verification failed and the OS is treating it as an ordinary, unverified link match.
Three things that quietly break it after launch
The JSON has to be served with the right content type and no redirect. A CDN or reverse proxy that 301-redirects /.well-known/assetlinks.json to a canonical URL, or serves it as text/html, fails verification even though a browser renders it fine. Curl it directly and check both:
curl -sI https://mfkapps.com/.well-known/assetlinks.json | grep -i content-type
Re-signing the app changes the fingerprint. Rotating a signing key, switching from a local keystore to Play App Signing, or onboarding a new build pipeline all silently break every previously-verified link until assetlinks.json is updated with the new fingerprint. This is worth a line in a release checklist, because the failure mode — links quietly downgrading to browser fallback — produces no crash and no error report.
Multiple apps sharing one domain need multiple statements. If more than one app on the same team claims paths under the same host, assetlinks.json is a JSON array — one object per app, each with its own package_name and fingerprint list. A file with only one app’s statement silently fails verification for every other app on that domain.
What this earns you beyond a nicer tap
Once a domain is verified, the same intent filter also lets the app act as the default handler for that domain everywhere the OS hands off a URL — a notification action, a QR code, a link shared from another app, a Smart Lock credential flow. None of that needs separate wiring; it’s the same autoVerify filter and the same assetlinks.json, doing more once the OS actually trusts the claim behind it.
If a link into the app still opens a browser after all of this, check pm get-app-links before touching the manifest again — in practice the intent filter is almost never the broken half.
// Related reading
More from the journal
WorkManager in 2026: unique work, chaining, and testing background jobs on Android
A practical guide to WorkManager on Android — unique work policies, chaining, expedited work, and how to actually test a background job before it ships.
The Android In-App Review API: asking for a rating without being annoying
A practical guide to Google's In-App Review API — how it actually works, where to trigger it, and why the usual 'rate us' popup is quietly hurting your Play Store rating.
Android app shortcuts and Quick Settings Tiles: logging water without opening the app
A practical guide to Android's dynamic ShortcutManager API and TileService — how to let a one-tap action skip the app entirely, with the pitfalls that trip most implementations.