Android · App Links

Android App Links not verifying: fix the assetlinks.json SHA-256 fingerprint mismatch

Your assetlinks.json is in the right place, the JSON is valid, the package name is correct — and your App Links still open in Chrome instead of your app. Nine times out of ten the culprit is one value: the SHA-256 signing fingerprint no longer matches the key your app is actually signed with.

The reason it is so common is Play App Signing: Google re-signs your app with a key you may have never looked at, and the fingerprint that matters is that key's — not the upload key on your machine.

The symptom

What the file has to say

Android reads Digital Asset Links from exactly this URL, over HTTPS, served directly, as a JSON array of statements:

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.yourcompany.yourapp",
      "sha256_cert_fingerprints": [
        "AB:CD:EF:12:34:56:78:90:...:FF"
      ]
    }
  }
]

Three things must all be true:

It is the third that bites.

Why the fingerprint drifts: Play App Signing

When you enroll in Play App Signing (the default for new apps), there are two keys:

Users install the app signed with Google's key. So the fingerprint Android checks at verification time is the app signing key's SHA-256 — and if you generated assetlinks.json from your local keystore, it will be wrong even though it looks legitimate. This is also why it breaks after a migration: enrolling an existing app into Play App Signing, rotating the key, or different flavors using different keys all change which fingerprint users' installs present.

Get the fingerprint that actually matters

The source of truth is the Play Console, not your laptop:

Play Console → your app → Test and release → App integrity → App signing → copy the SHA-256 certificate fingerprint under App signing key certificate.

Google even gives you a ready-made assetlinks.json on that screen (Digital Asset Links JSON) — use it; it already has the correct fingerprint. To compute one from a keystore or the artifact yourself:

# From a keystore
keytool -list -v -keystore my-release-key.jks -alias my-alias | grep SHA256

# Straight from the artifact users run
keytool -printcert -jarfile app-release.aab | grep -i sha256

List every key that can sign an install

You usually want more than one fingerprint, because installs can be signed by different keys:

Add all of them to sha256_cert_fingerprints. The check passes as long as the install's key is present; listing extras is safe.

Diagnose on a device (Android 12+)

On Android 12 and newer, you can inspect and force domain verification with adb:

# Show verification state for each declared domain
adb shell pm get-app-links com.yourcompany.yourapp

# Re-trigger verification
adb shell pm verify-app-links --re-verify com.yourcompany.yourapp

# Manually approve locally to confirm the intent-filter side is correct
adb shell pm set-app-links --package com.yourcompany.yourapp 1 all

If the domain becomes verified only after the manual set-app-links, your manifest intent filter is fine and the failure is server-side — the assetlinks.json fingerprint. Confirm the manifest has an autoVerify intent filter for the domain:

<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="yourdomain.com" />
</intent-filter>

Don't forget the delivery

Like iOS's AASA, the Android verifier reads assetlinks.json directly and does not follow redirects. The same apex ↔ www 301 trap applies (ASSETLINKS_REDIRECT), as does serving HTML/login pages to anonymous clients. Quick check, again without -L:

curl -sS -D - -o /dev/null \
  https://yourdomain.com/.well-known/assetlinks.json

You want a direct 200 with a JSON array body. A 3xx, a 404, or an HTML body all mean the file never reaches Google's verifier.

Fix checklist