Skip to main content

assetlinks.json for Android App Links: The Complete Guide

Android App Links — the mechanism that makes HTTPS links open your app directly, with no "open with" chooser — are verified through a small JSON file on your web server: assetlinks.json, part of Google's Digital Asset Links system. Where iOS trusts a file that names the app, Android goes further and verifies your app cryptographically, checking the file's statements against your app's signing certificate. That difference is why the most common App Links failure, by far, is a wrong fingerprint in this file.

This guide covers the file's format, where the fingerprints come from, hosting requirements, and the manifest side of the handshake.

What Digital Asset Links are

A Digital Asset Links statement is a machine-readable claim: "this website delegates this permission to this app." For App Links, the claim is that URLs on your domain may be handled by your Android app. Android fetches the statements from your domain at install time and, if the certificate in the statement matches the certificate the app was actually signed with, it verifies your domain and stops showing the chooser for matching links.

The JSON format

The file is a JSON array of statement objects:

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.myapp",
      "sha256_cert_fingerprints": [
        "AA:BB:CC:DD:EE:F0:12:34:56:78:9A:BC:DE:F0:11:22:33:44:55:66:77:88:99:00:11:22:33:44:55:66:77:88"
      ]
    }
  }
]

Field by field:

  • relation — what's being delegated. App Links use delegate_permission/common.handle_all_urls. (A sibling relation, delegate_permission/common.get_login_creds, enables Chrome password AutoFill from your site into your app — unrelated to deep linking, but it lives in the same file.)
  • target.namespace — "android_app" for app link statements.
  • target.package_name — the app's application ID exactly as it appears in your build config.
  • target.sha256_cert_fingerprints — the SHA-256 fingerprints of the certificates that sign your app. This list is where Android's trust decision actually happens.

Finding your SHA-256 fingerprints

Any certificate that may sign a shipped build of your app belongs in the array. Extract each with keytool:

# Debug certificate (local development installs)
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey \
  -storepass android -keypass android

# Release certificate (your upload keystore)
keytool -list -v -keystore my-release-key.jks -alias my-alias

Copy the SHA256: line — the colon-separated hex string — into the array.

Now the trap: if your app is distributed through Google Play with Play App Signing (the default), Play re-signs your APK with Google's app signing key, not your upload key. The fingerprint users' devices actually see is the app signing key's, found in Play Console under Release → Setup → App signing. List it alongside (or instead of) your upload certificate; the debug and Play signing fingerprints in one array is a perfectly normal production file.

Multiple apps, one file

The array can hold any number of statements. A production app, a beta flavor, and a companion app on the same domain are just three entries:

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": { "namespace": "android_app",
                "package_name": "com.example.myapp",
                "sha256_cert_fingerprints": ["AA:BB:…"] }
  },
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": { "namespace": "android_app",
                "package_name": "com.example.myapp.beta",
                "sha256_cert_fingerprints": ["99:88:…"] }
  }
]

Android matches statements to installed apps by package name, so unrelated entries coexist harmlessly.

Hosting requirements

  • Exact path: https://yourdomain.com/.well-known/assetlinks.json.
  • HTTPS only, no redirects — the file must be served directly.
  • Content-Type: application/json, and valid JSON (a trailing comma fails the whole verification).
  • Accessible without cookies or login — the verifier is an unauthenticated fetch.

Like iOS's AASA file, failures here are mostly silent: an unreadable file just means your domain never verifies.

The other half: your AndroidManifest

The file on the server is only half the handshake. The app must opt in with an intent filter marked for verification:

<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="links.example.com"
        android:pathPrefix="/product" />
</intent-filter>

Without android:autoVerify="true", Android won't even attempt verification and every link shows the chooser. The host must match the domain serving your assetlinks.json.

Validating your setup

  1. Check the raw response: curl -I https://yourdomain.com/.well-known/assetlinks.json — 200 over HTTPS, application/json, no redirect.
  2. Check verification status on device: adb shell pm get-app-links com.example.myapp — your domain should show as verified.
  3. Test a link: tap an https://links.example.com/product/123 URL from Messages or Notes; it should open your app with no chooser. The system's "Open by default" settings page shows the same verdict per domain.

Troubleshooting common issues

  • Domain shows "unverified": almost always the fingerprint — you listed the upload keystore but Play signs with the app signing key. Re-check Play Console.
  • Verification worked, then stopped after a Play update: you switched signing schemes or added a new flavor without adding its certificate to the array.
  • Chooser still appears: missing autoVerify, a host mismatch between manifest and file, or the file failed to fetch — re-run the curl check.
  • Stale verification: Android re-verifies on app install and update; toggling the app's "Open by default" setting off and on, or reinstalling, forces a refresh.

Skip the JSON hand-editing with DecTog

DecTog generates and serves assetlinks.json for your link domain from your app's dashboard configuration — package name and signing fingerprints — at the right path with the right headers, and keeps the iOS AASA file in sync with the same settings. You own the domain; DecTog keeps both verification files correct.

New to the topic? Start with What is Deep Linking? or What is Deferred Deep Linking?