Glossary/Implementation artifacts
assetlinks.json
Definition
assetlinks.json is a Digital Asset Links statement file hosted at a domain's /.well-known/ path that authorises a named Android app, identified by package name and signing certificate fingerprint, to handle that domain's URLs.
It is the Android counterpart to iOS's apple-app-site-association, and the two are close enough in purpose to be confusing in their differences. Android verifies the statement at install time and caches the result; the file is a claim by the *domain* about which app it trusts, which is why the fingerprint in it has to match the key that signs the app users actually receive.
What the file looks like
The file is a JSON array of statements, not an object. That trips people up because the iOS equivalent is an object, and an assetlinks.json that starts with { fails to parse.
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.shop",
"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",
"7B:14:12:6C:1A:99:2E:5B:57:B1:0F:D8:D1:C2:24:9A:8D:4C:03:5F:9E:B7:22:A1:44:8E:D0:6C:19:33:F0:2B"
]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.shop.debug",
"sha256_cert_fingerprints": [
"A1:9F:3C:22:B8:07:41:6E:5D:90:AC:11:74:33:2F:88:5B:C0:DE:47:19:6A:B3:52:E8:0D:71:CC:34:96:1F:AA"
]
}
}
]| Field | Value | Where it comes from |
|---|---|---|
relation | ["delegate_permission/common.handle_all_urls"] | Fixed string for App Links. common.get_login_creds is a separate relation for password autofill |
target.namespace | "android_app" | Fixed. "web" is for site-to-site statements, not apps |
target.package_name | com.example.shop | The applicationId from Gradle — not the Java package |
target.sha256_cert_fingerprints | Uppercase hex, colon-separated, 32 bytes | The key that signs the app as users receive it |
One statement per package, not per fingerprint
sha256_cert_fingerprints is an array, so one statement covers every key that signs the same package — release, upload, and debug together. A separate statement is only needed for a *different* package_name, which is what build variants with an applicationIdSuffix produce.
Which fingerprint, and where to get it
This is the field that goes wrong, and it goes wrong for a structural reason: if you distribute through Google Play with Play App Signing — the default for every app created since 2021 — Google re-signs your app with a key you do not hold and never see. The fingerprint in the file must be Google's app signing key, not the upload key you sign with locally. Both are printed on the same Play Console screen, one above the other.
# All variants at once, from the project root — the fastest option
./gradlew signingReport
# The shared debug keystore
keytool -list -v \
-keystore ~/.android/debug.keystore \
-alias androiddebugkey -storepass android -keypass android
# A release keystore you hold
keytool -list -v -keystore release.jks -alias upload
# From an APK you already built
keytool -printcert -jarfile app-release.apk
# The one that actually ships, if you use Play App Signing:
# Play Console → Test and release → Setup → App signing
# → "App signing key certificate" → SHA-256| How the build reaches the device | Signed by | Fingerprint to list |
|---|---|---|
| Google Play (any track, including internal testing) | Play app signing key | |
| Direct APK download, or a sideloaded release build | Your release keystore | Release key |
./gradlew installDebug, Android Studio Run | Debug keystore | Debug key |
| Firebase App Distribution, App Center | Whatever key you uploaded with | That key |
| Samsung Galaxy Store, Amazon Appstore | Your release keystore | Release key |
Internal testing tracks are still Play-signed
A common misdiagnosis is "App Links work in the internal track but not in production", or the reverse. Both come from Play and are signed with the same app signing key, so the fingerprint is not the difference. Look at package_name instead — an applicationIdSuffix on one variant is the usual explanation.
Hosting rules
Android's verification agent fetches the file over the network at install time. The rules are close to Apple's, close enough that a domain misconfigured for one is usually misconfigured for both — which is worth knowing, because it means one infrastructure fix repairs both platforms.
assetlinks.json (Android) | apple-app-site-association (iOS) | |
|---|---|---|
| Path | /.well-known/assetlinks.json | /.well-known/apple-app-site-association |
| File extension | .json, required | None, and adding one breaks it |
| Content type | application/json | application/json |
| Redirects | Not permitted | Not permitted |
| Top-level JSON type | Array | Object |
| Fetched by | The device, at install time | Apple's CDN, then cached |
| Propagation delay | None — direct fetch | Up to 24 hours through the CDN |
| Re-check after an edit | Reinstall, or pm verify-app-links --re-verify | Reinstall, or ?mode=developer |
| Size limit | None documented | 128 KB, enforced |
The propagation row is the practically important one. Android has no CDN in the path, so a corrected file is live the moment you deploy it — but the *device* still holds its cached verdict from install time, so nothing changes on an already-installed app until it reinstalls or you force a re-verify.
Delegating to another host
A statement can point at a file on a different domain with an include statement, which is how a large estate keeps one canonical list. It adds a second fetch that must also satisfy every rule above, so it trades a maintenance problem for a reliability one — usually not worth it for a handful of domains.
The manifest side
The file is only half of the handshake. It is the domain's claim about the app; the manifest is the app's claim about the domain, and verification only runs when both exist. android:autoVerify="true" is what asks the system to go and check.
<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="example.com" />
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>Every host named in an autoVerify intent filter needs its own assetlinks.json, served from that exact host. example.com and www.example.com are two domains and two files. If a host in the manifest no longer serves one — a decommissioned staging domain is the classic case — it fails verification, and on Android 11 and earlier that failure took every other host in the filter down with it.
assetlinks.json generator
Enter your package name and fingerprints and get a valid statement file, or point it at a live domain to fetch the existing one and check the redirect chain, content type, JSON shape, and relation string. It generates the iOS association file in the same pass, since the two are almost always deployed together.
Open the assetlinks.json generator →Frequently asked questions
- What is assetlinks.json used for?
- It is the Digital Asset Links statement file that tells Android which app is allowed to handle a domain's URLs. Hosted at https://yourdomain.com/.well-known/assetlinks.json, it names an app by package name and signing certificate fingerprint. Android fetches and verifies it when the app is installed, and without it, links to the domain open in the browser rather than the app.
- Which SHA-256 fingerprint should go in assetlinks.json?
- The fingerprint of the key that signs the app as users actually receive it. With Play App Signing, which is the default, Google re-signs your app with its own key, so you need the app signing key certificate from Play Console under Test and release, Setup, App signing — not the upload key listed on the same screen. Because the field is an array, listing the Play app signing key, your release key, and the debug key together avoids having to choose.
- Can I list multiple apps in one assetlinks.json file?
- Yes. The file is a JSON array of statements, so add one object per package name. Multiple signing certificates for the same package go in that package's sha256_cert_fingerprints array rather than in separate statements. Build variants that use an applicationIdSuffix have different package names and therefore need their own statements.
- Why does assetlinks.json start with a square bracket?
- Because the Digital Asset Links format is a list of statements rather than a single object. This differs from Apple's apple-app-site-association file, which is a JSON object starting with a brace. An assetlinks.json file that begins with a brace will not parse and verification will fail with no visible error.
- Do I need to reinstall the app after changing assetlinks.json?
- Yes, or force re-verification with adb shell pm verify-app-links --re-verify followed by the package name. Unlike iOS there is no CDN in the path, so the corrected file is live as soon as it is deployed, but Android caches the verification verdict from install time and does not re-check because the file changed.
- What is the difference between assetlinks.json and Digital Asset Links?
- Digital Asset Links is the protocol — a specification for a domain to make verifiable statements about apps and other domains it trusts. assetlinks.json is the file that carries those statements. The protocol also covers relations other than App Links, such as delegate_permission/common.get_login_creds for sharing saved passwords between a site and an app.
Related terms
- Apple App Site Association (AASA) — The apple-app-site-association file is a JSON document hosted at a domain's /.well-known/ path that tells iOS which app is allowed to handle which URLs on that domain.
- Validating assetlinks.json — Validating assetlinks.json means confirming four separate things: that Android can fetch the file, that it parses as a valid statement list, that it names the fingerprint of the shipped build, and that verification passed on a device.
- Android App Links not working — Android App Links fail when the system cannot verify that a domain and an app belong to the same owner, at which point tapped links open in the browser instead of the app with no error shown to the user.