On this page
scope
The complete reference for the Android Digital Asset Links file (assetlinks.json). Format, fingerprint requirements, hosting, verification with adb and Google's API. This is the format spec — for the setup walkthrough, see /guides/android-app-links-setup. For the iOS equivalent, see /guides/aasa-file-explained.
Cluster C hub: /guides/firebase-dynamic-links-replacement.
the file
A JSON file served at:
https://yourdomain.com/.well-known/assetlinks.json
The file declares which Android apps are authorized to handle URLs on your domain. Android fetches and verifies it at app install time. If verification succeeds, App Links resolve directly to your app; if it fails, the system surfaces the "Open with" disambiguation dialog.
Hosting requirements:
| Property | Required value |
|---|---|
| Scheme | https:// |
| Path | /.well-known/assetlinks.json |
| Content-Type | application/json |
| Redirects | None |
| TLS | Publicly trusted certificate |
| Authentication | None — public anonymous GET |
| File size | Under a few KB typical; no hard cap documented |
| Subdomains | Each subdomain requires its own assetlinks.json |
the format
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourcompany.yourapp",
"sha256_cert_fingerprints": [
"14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"
]
}
}
]
The file is a JSON array of "statements." Each statement declares a relationship between a web property and a target. For App Links, the target is an Android app.
Fields:
| Key | Type | Required | Meaning |
|---|---|---|---|
relation[] |
array of strings | Yes | Relationship URI. For App Links: delegate_permission/common.handle_all_urls |
target.namespace |
string | Yes | android_app for App Links, web for other relations |
target.package_name |
string | Yes | The app's applicationId (matches AndroidManifest package) |
target.sha256_cert_fingerprints[] |
array of strings | Yes | One or more SHA-256 cert fingerprints |
The sha256_cert_fingerprints array supports multiple fingerprints — required when you have separate debug + release builds, or both upload key + Play App Signing key.
getting the SHA-256 fingerprint
From a local keystore
keytool -list -v -keystore /path/to/keystore.jks -alias your-alias
Look for the SHA256: line. Format is 14:6D:E9:... — 64 hex characters separated by colons.
From the debug keystore (auto-created by Android Studio)
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
This is the fingerprint your Run button signs with. Required if you're testing App Links on a debug build.
From Google Play App Signing
When you opt into Play App Signing, Google re-signs your APK/AAB with a Play-managed key when distributing to users. The fingerprint Android verifies against is the Play-managed one, not your upload key.
In Play Console: → Your App → Setup → App Integrity → App Signing → "App signing key certificate" → copy SHA-256 certificate fingerprint.
Most production apps need BOTH fingerprints in assetlinks.json:
- The upload-key fingerprint (used when sideloading or installing via Android Studio).
- The Play-managed key fingerprint (used when installing from the Play Store).
Without the Play-managed fingerprint, verification fails for users who install from Play.
multiple apps, multiple domains
Multiple apps claiming the same domain:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourcompany.app",
"sha256_cert_fingerprints": ["<app fingerprint>"]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourcompany.app.beta",
"sha256_cert_fingerprints": ["<beta fingerprint>"]
}
}
]
Each app is a separate statement.
Multiple domains for a single app: each domain serves its own assetlinks.json. Subdomains do NOT inherit from parent domains. https://yourdomain.com/.well-known/assetlinks.json does not cover https://app.yourdomain.com/.
the web namespace (rare for App Links)
The web namespace declares a relationship between two web properties — most commonly, that one domain owns another. Not used for App Link verification, but appears in adjacent contexts (e.g., Google Sign-In domain ownership).
{
"relation": ["delegate_permission/common.get_login_creds"],
"target": {
"namespace": "web",
"site": "https://login.yourdomain.com"
}
}
For Universal/App Link work, you'll only use android_app namespace statements. Mentioned here for completeness.
hosting
Nginx
location = /.well-known/assetlinks.json {
default_type application/json;
add_header X-Content-Type-Options nosniff;
try_files /assetlinks.json =404;
}
Apache (.htaccess)
<Files "assetlinks.json">
ForceType application/json
</Files>
Cloudflare Workers
addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (url.pathname === '/.well-known/assetlinks.json') {
event.respondWith(new Response(JSON.stringify(statements), {
headers: { 'content-type': 'application/json; charset=utf-8' }
}));
}
});
Next.js / Vercel
Place at public/.well-known/assetlinks.json. The file extension is part of the URL on Android (unlike AASA, which is extensionless), so Vercel serves it correctly by default.
verifying the file
Google's Statement List API
The authoritative check — this is what Android uses internally.
curl -s "https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://yourdomain.com&relation=delegate_permission/common.handle_all_urls"
Expected response: a list of statements matching the file, with maxAge indicating Google's cache freshness.
If the response is empty or missing your statement, Android will not verify the App Link. Causes:
- assetlinks.json not reachable from Google's crawler IPs.
- Content-Type wrong.
- File has malformed JSON.
- Cloudflare or other CDN blocking the Google crawler User-Agent.
adb shell on a real device
After installing the app on a test device:
adb shell pm get-app-links com.yourcompany.app
Expected output:
com.yourcompany.app:
ID: ...
Signatures: [...]
Domain verification state:
yourdomain.com: verified
If the state is none (Android 10 and earlier) or 1024 (Android 11+, legacy unverified):
adb shell pm verify-app-links --re-verify com.yourcompany.app
This forces re-verification. For Android 12+ (API 31+), the user-preference state may need a reset:
adb shell pm set-app-links --package com.yourcompany.app 0 all
common assetlinks.json failures
| Symptom | Cause | Fix |
|---|---|---|
pm get-app-links shows none after install |
File not reachable, or wrong fingerprint | Check curl + verify fingerprint |
| Works on debug build, fails after Play Store release | Play App Signing fingerprint missing | Add Play-managed cert fingerprint |
| Works after sideload, fails after Play Store install | Same as above | Same as above |
| Verification fails sporadically | Cloudflare bot challenge blocking Google crawler | Allow Google crawler User-Agent through bot protection |
| Verification fails on Android 12+ specifically | User-preference state set to 0 |
Reset with pm set-app-links |
| Some links open app, others don't | intent-filter pathPrefix doesn't cover all URLs |
Check AndroidManifest declaration |
| Multiple apps on same domain, wrong one opens | First-tap user picks, sticky thereafter | Document the disambiguation UX |
The Play App Signing fingerprint mismatch is the single most common production bug. Always include both upload-key and Play-managed fingerprints.
fingerprint extraction shortcuts
Inside Gradle build
You can extract the fingerprint at build time and verify it matches your assetlinks.json:
// build.gradle.kts (Module: app)
android {
// ...
}
tasks.register("printSha256") {
doLast {
val keystore = file(System.getenv("KEYSTORE_PATH") ?: "${System.getProperty("user.home")}/.android/debug.keystore")
exec {
commandLine("keytool", "-list", "-v",
"-keystore", keystore.absolutePath,
"-alias", "androiddebugkey",
"-storepass", "android",
"-keypass", "android")
}
}
}
Run with ./gradlew printSha256.
From an installed APK
keytool -printcert -jarfile path/to/app.apk
Useful for checking what fingerprint a downloaded APK is signed with.
related dev reading
- Android setup: /guides/android-app-links-setup
- iOS AASA (the equivalent): /guides/aasa-file-explained
- iOS setup: /guides/ios-universal-links-setup
- Android intent URL fallback: /guides/intent-url-android
- Cluster C hub: /guides/firebase-dynamic-links-replacement
- Bridge: /guides/in-app-browser-logged-out
For non-app linking where assetlinks.json doesn't apply, the no-SDK linkboo path is at link.boo/api.
references
- Google Digital Asset Links protocol specification
- Android App Links Verification documentation
- Play App Signing documentation
- Google Statement List API reference