# Android App Links autoVerify Failed? 12 Causes and Fixes

> autoVerify failed and links open the browser or a chooser? Check assetlinks.json with curl, then pm get-app-links on the device. 12 causes, each with a fix.

_Last updated: 2026-10-26_

> **TL;DR:** Android App Links autoVerify fails for 12 reasons, and the top production cause is an `assetlinks.json` that lists the upload key instead of the Play app signing key. Confirm the file with `curl -sI https://yourdomain.com/.well-known/assetlinks.json` (a direct `200`, no redirect). Then run `adb shell pm get-app-links com.example.myapp` and expect `verified` next to each host.

No terminal handy? Paste your domain into the [free assetlinks.json validator](https://warplink.app/tools/assetlinks-validator).

The 12 reasons fall into four layers: server (1 to 3), SHA-256 fingerprints (4 to 6), package and manifest (7 to 11), and device state (12).

## Find the Failing Layer First

App Link verification has four layers: the server, the file contents, the manifest, and the device. Run the checks in this table from top to bottom. The first row whose result does not match is where to start.

| Symptom | Check | Expected result | Reasons |
|---------|-------|-----------------|---------|
| Verification never completes, or every host fails | `curl -sI https://yourdomain.com/.well-known/assetlinks.json` | `200`, `content-type: application/json`, no `Location` header | [1 to 3](#1-a-redirect-sits-in-front-of-the-file) |
| Works in debug, fails from Play | Play Console, App integrity, Play app signing | The file lists the app signing key SHA-256, in uppercase | [4 to 6](#4-the-file-lists-the-upload-key-instead-of-the-play-app-signing-key) |
| One flavor fails | `adb shell pm list packages \| grep yourapp` | The installed ID equals `package_name` | [7](#7-the-package_name-does-not-match-the-installed-applicationid) |
| A chooser dialog appears | Open `AndroidManifest.xml` | `android:autoVerify="true"` on an `https` filter with the exact host | [8 to 11](#8-the-intent-filter-has-no-autoverify) |
| The file and manifest look right | `adb shell pm get-app-links com.example.myapp` | `verified` next to each host | [12](#12-the-device-is-holding-stale-or-unfinished-state) |

Each reason follows one pattern: the symptom, the check, and the fix. App Links fail quietly. There is no crash and no error toast. The link opens a browser or an "Open with" chooser, and you are left guessing. A developer writing on Medium in late 2025 captured it: "Okay, here's where I wasted like 2 days of my life... the annoying SHA-256 certificate issue that had me pulling my hair out for days." For the iOS equivalent, see the [Universal Links Open Safari? Fix Your apple-app-site-association File](https://warplink.app/blog/apple-app-site-association-not-working) and [Universal Links Not Working? 15 Causes and the Fix for Each](https://warplink.app/blog/universal-links-not-opening).

## 1. A Redirect Sits in Front of the File

**Symptom:** Verification fails for every host, and the file looks fine in a browser.

**Check:**

```bash
curl -sIL https://yourdomain.com/.well-known/assetlinks.json
```

Look for any `301` or `302`. Android's troubleshooting guide says the system will not verify any App Links for your app if a redirect exists such as `http://example.com` to `https://example.com`, or `example.com` to `www.example.com`.

**Fix:** Remove the redirect so the file resolves with a direct `200` on the exact host in your manifest. This is the most common server-side cause.

## 2. The File Is Not Served as Valid JSON at the Right Path

**Symptom:** The URL answers `200` and the device still records a failure.

**Check:** The file must live at exactly `https://yourdomain.com/.well-known/assetlinks.json`. Unlike the iOS file, it has a `.json` extension. Confirm the content type and the body:

```bash
curl -sI https://yourdomain.com/.well-known/assetlinks.json
curl -s https://yourdomain.com/.well-known/assetlinks.json | jq .
```

Android's docs say the file is served as `application/json`, so do not serve `text/plain` or `text/html`. If `jq` errors, the file is not valid JSON: a trailing comma, a stray comment, or a byte-order mark breaks parsing. The format is a JSON array of statement objects:

```json
[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.myapp",
      "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"
      ]
    }
  }
]
```

**Fix:** Move the file to `/.well-known/`, correct the content type, and repair the JSON. To check the same things without a terminal, paste your domain into our [free assetlinks.json validator](https://warplink.app/tools/assetlinks-validator).

## 3. The Verifier Cannot Reach the File

**Symptom:** The file works from your laptop and not for the verifier.

**Check:** Look for four blockers. A login wall, basic auth, or a bot-protection challenge blocks the verifier, so the `.well-known` path has to be public. The HTTPS certificate must be valid and trusted: a self-signed or expired certificate fails, and there is no "proceed anyway." Android's troubleshooting guide also says `example.com.` (with a trailing period) must serve the same content as `example.com`. The device itself needs network access during verification.

**Fix:** Open the path to the public, renew or replace the certificate, and make the trailing-period host answer like the normal one.

## 4. The File Lists the Upload Key Instead of the Play App Signing Key

**Symptom:** App Links verify on a build you signed locally and fail on the build installed from Google Play.

**Check:** With Play App Signing, two keys exist. The upload key proves an upload is from you. Google's app signing key re-signs the app before distribution, and that is the certificate on the user's device. Android's docs say that the value from running `keytool` locally will usually not match the one on users' devices. Open Play Console, go to **App integrity > Play app signing**, and compare the **App signing key certificate** SHA-256 with your file.

**Fix:** Add the **App signing key certificate** SHA-256 to `sha256_cert_fingerprints`. Do not copy the **Upload key certificate** value. The same page shows a ready-made Digital Asset Links snippet.

## 5. A Signing Key You Actually Install Is Missing From the List

**Symptom:** Store builds verify and your internal APK or debug build does not, or the reverse.

**Check:** Read the SHA-256 of the build that fails.

```bash
# Debug build
keytool -list -v -keystore ~/.android/debug.keystore \
  -alias androiddebugkey -storepass android -keypass android

# Locally signed release
keytool -list -v -keystore release.jks -alias your-alias

# Straight from a built APK (the most reliable)
apksigner verify --print-certs app-release.apk
```

**Fix:** The array is a list on purpose, and Android documents it as the way to support different versions of your app, such as debug and production builds. Add every certificate that signs a build you want verified:

```json
"sha256_cert_fingerprints": [
  "AA:BB:...:Play app signing key",
  "CC:DD:...:release key, only if you install builds signed with it",
  "EE:FF:...:debug key, if you want debug builds verified"
]
```

Google's [Statement List Generator](https://developers.google.com/digital-asset-links/tools/generator) outputs the exact JSON from a domain, package name, and SHA-256. Our [assetlinks.json generator](https://warplink.app/tools/assetlinks-generator) builds the same file.

## 6. The SHA-256 Has the Wrong Format

**Symptom:** The right key is in the file and verification still fails.

**Check:** Android's troubleshooting guide says the value must be uppercase, and lists a lowercase value as a common mistake, alongside signing with a debug certificate while only the release value is in the file. The format is colon-separated hex pairs, such as `14:6D:E9:...`. A single wrong character fails verification with no useful error.

**Fix:** Copy the value from `apksigner` or Play Console rather than retyping it, keep it uppercase, and keep the colons.

## 7. The package_name Does Not Match the Installed applicationId

**Symptom:** One flavor or build type verifies and another does not.

**Check:** The `package_name` must exactly match the `applicationId` installed on the device, not the manifest `package` attribute if you remap it and not a flavor suffix you forgot.

```kotlin
// build.gradle.kts
android {
    defaultConfig { applicationId = "com.example.myapp" }
    productFlavors {
        create("pro") { applicationIdSuffix = ".pro" } // -> com.example.myapp.pro
    }
}
```

If the device runs the `pro` flavor, the verifier looks for `com.example.myapp.pro`. Confirm the installed ID with `adb shell pm list packages | grep example`.

**Fix:** Add a statement object for each `applicationId` you ship. Add a second object rather than cramming two packages into one.

## 8. The Intent Filter Has No autoVerify

**Symptom:** The link works but shows a chooser dialog instead of opening the app directly, and `pm get-app-links` shows `none` after you waited and re-ran verification.

**Check:** Android's troubleshooting guide says each `<intent-filter>` must include `android:autoVerify="true"`. Without it, Android never fetches your `assetlinks.json`. No `autoVerify`, no verification.

**Fix:**

```xml
<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" />
  <data android:pathPrefix="/product" />
</intent-filter>
```

## 9. The Filter Has the Wrong Shape

**Symptom:** `autoVerify` is set and the host never appears in the verification output.

**Check:** Verification only runs for a filter that uses the `http` or `https` data scheme (not a custom scheme) and includes the `VIEW` action plus the `BROWSABLE` and `DEFAULT` categories. If any is missing, the system does not treat the filter as an App Link.

**Fix:** Match the filter in reason 8 exactly, and keep the scheme as `https`.

## 10. The Host in the Manifest Is Not the Host You Link To

**Symptom:** Links to `yourdomain.com` verify and links to `go.yourdomain.com` do not.

**Check:** `android:host="yourdomain.com"` does not cover `www.yourdomain.com` or `links.yourdomain.com`. The host in the filter, the host in your links, and the host serving `assetlinks.json` must be the same string. Android's docs say that if your app links support multiple host domains, you must publish `assetlinks.json` on each domain.

**Fix:** Declare every host you link to, and serve a correct file at each one's own `/.well-known/assetlinks.json`. A wildcard host like `*.yourdomain.com` needs a reachable file at each subdomain you actually use.

## 11. One Broken Host Fails the Rest

**Symptom:** Hosts you configured perfectly fail too.

**Check:** On Android 11 and lower, the app becomes the default handler only if the Digital Asset Links files match all the hosts in the manifest. One typo or one host without a file can fail all of them. Android's guide also says test-only non-verifiable links can fail verification for all App Links on Android 11 and lower. Android 12 and later query the file for each unique host name. Curl every host:

```bash
for h in yourdomain.com go.yourdomain.com share.yourdomain.com; do
  echo "== $h =="
  curl -sI "https://$h/.well-known/assetlinks.json" | head -n 1
done
```

**Fix:** Fix or remove the broken host, and keep only hosts you can serve the file from. A failed host falls back to the chooser dialog on every version.

## 12. The Device Is Holding Stale or Unfinished State

**Symptom:** The server, file, and manifest all pass and the device still reports a failure.

**Check:** Ask the device what it recorded. Wait at least 20 seconds after an install, because verification is asynchronous.

```bash
adb shell pm get-app-links com.example.myapp
```

Add `--user cur` to read the state for the current user. Read the state next to each domain:

| State | Meaning | Next step |
|---|---|---|
| `verified` | The domain passed and the app handles its links | Done |
| `none` | Nothing recorded yet; verification may still be running | Wait a few minutes, re-verify, check again; if it stays `none`, check `autoVerify` |
| `1024` or higher | A device-specific verifier code, not one cause | Confirm network access, re-verify, check the file (reasons 1 to 3) and the SHA-256 (4 to 6) |
| `legacy_failure` | The legacy verifier rejected the domain for an unknown reason | Reset and re-verify |
| `approved` / `denied` | Force-set through the shell, not earned by verification | Fine for testing |
| `migrated` / `restored` | Preserved from a legacy result or a data restore | Re-verify to confirm |
| `system_configured` | Approved by the device configuration | Not an error |

**Fix:** On Android 12 and later, clear stale state and verify again (the reset command comes from Android's verification guide):

```bash
# Reset the recorded state for all of this package's domains
adb shell pm set-app-links --package com.example.myapp 0 all

# Trigger a fresh verification pass
adb shell pm verify-app-links --re-verify com.example.myapp

# Confirm
adb shell pm get-app-links com.example.myapp
```

Android's documentation says to wait a few minutes for a manual verification. Check **Settings > Apps > your app > Open by default > Add link** too: a verified link shows as supported. On Android 15 and later, the system also re-verifies in the background, and a change to your file can take up to seven days to reach devices that do not re-verify manually. On Android 14 and lower, the system picks up a change only at install or update. The reliable reset is the three commands above, or an uninstall and reinstall.

To compare with what Google sees, query the Digital Asset Links API, the endpoint the verifier uses:

```bash
curl -s "https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://yourdomain.com&relation=delegate_permission/common.handle_all_urls" | jq .
```

A healthy response has a `statements` array, and each entry carries `target.androidApp.packageName` and `target.androidApp.certificate.sha256Fingerprint`. An empty response sends you back to reasons 1 to 3. A SHA-256 that differs from your device's is reason 4 in black and white. On Android 17 and later, `adb shell am start --debug-link -a android.intent.action.VIEW -d "https://yourdomain.com/product/123"` shows which apps and intent filters match a URL.

## The Short Version

```
App Link opens a chooser / browser, not the app
        │
        ▼
curl -sI the assetlinks.json URL ──► not 200 / redirect / wrong type / blocked? ──► reasons 1 to 3
        │ ok
        ▼
SHA-256 = Play APP SIGNING key, uppercase, every signing key listed? ──► no ──► reasons 4 to 6
        │ yes
        ▼
package_name matches the installed applicationId? ──► no ──► reason 7
        │ yes
        ▼
intent-filter has autoVerify + https + exact host, every host served? ──► no ──► reasons 8 to 11
        │ yes
        ▼
adb pm get-app-links shows "verified"? ──► no ──► reset + re-verify, then wait (reason 12)
```

## How WarpLink Handles the Setup

Reasons 1 to 3 are about hosting a byte-perfect `assetlinks.json`. When you register an app in WarpLink, we generate the file from the package name and SHA-256 fingerprints you provide and serve it with `Content-Type: application/json`, no redirects, and no auth wall. Each app has its own link host, `{handle}.aplnk.to`, with its own assetlinks and AASA pair, because iOS and Android match a link host to one app. Verified custom domains get their own pair, and the files are regenerated when you update the app.

To check the result, open `https://{host}/test` on your app's host. It is a setup page served at the edge, so you can confirm the host is live before you run `pm get-app-links`. On your side, set the `autoVerify` filter with the exact host and register the Play App Signing SHA-256 from Play Console (reason 4). The SDK side is one call: pass the incoming intent URI to `WarpLink.handleDeepLink(uri)` from `onCreate()` and `onNewIntent()`, and it returns the destination, custom parameters, and attribution data on the main thread, for cold and warm start. The full setup is in the [Android deep links docs](https://warplink.app/docs/sdks/android/deep-links), and the [deep linking](https://warplink.app/deep-linking) page shows what an app registration generates.

## Frequently Asked Questions

**Why do my Android App Links work in debug but not in production?**
Almost always the SHA-256 certificate fingerprint. Your debug build is signed with your debug or upload key, but Play App Signing re-signs the distributed app with Google's signing key, which has a different SHA-256. Your `assetlinks.json` lists the wrong one, so `autoVerify` passes locally and fails in the Play Store build. Copy the App signing key SHA-256 from Play Console under App integrity > Play app signing and add it to the file.

**Where do I get the correct SHA-256 for assetlinks.json?**
For a Play-distributed app, copy it from Play Console: **App integrity > Play app signing**, under "App signing key certificate" (not "Upload key certificate"). For a locally signed release, read it from your release keystore with `keytool -list -v -keystore release.jks -alias your-alias`, or from a built APK with `apksigner verify --print-certs app-release.apk`. List every signing key you use in the `sha256_cert_fingerprints` array.

**What does state 1024 or legacy_failure mean in pm get-app-links?**
`1024` (or higher) is a device-specific verifier code, and `legacy_failure` is a rejection with an unknown reason. Neither names one cause. Confirm network access, re-run `adb shell pm verify-app-links --re-verify <package>`, then check the file and any redirect (reasons 1 to 3) and the SHA-256 (reasons 4 to 6). Reset with `adb shell pm set-app-links --package <pkg> 0 all`, re-verify, and wait a few minutes before you read the state.

**Why does autoVerify show a chooser dialog instead of opening my app?**
Either the intent filter is missing `android:autoVerify="true"` (so Android never verifies and defaults to the chooser), or verification ran and failed. Run `adb shell pm get-app-links <package>`: `none` means no result is recorded yet, so wait, re-run `adb shell pm verify-app-links --re-verify <package>`, and check again. `1024` or higher, or `legacy_failure`, has no single cause: confirm network access, then check the file and the SHA-256. Check the manifest and the JSON file as separate steps.

**How do I force Android to re-verify App Links without reinstalling?**
Run `adb shell pm verify-app-links --re-verify <package>`, then check the result with `adb shell pm get-app-links <package>`. On Android 12 and later, reset stale state first with `adb shell pm set-app-links --package <package> 0 all`. Wait a few minutes before you read the result, and note that re-verification needs network access. Reinstalling still works as a fallback, since Android re-verifies at install time.

**Does the SHA-256 in assetlinks.json need to be uppercase?**
Yes. Android's troubleshooting guide says the value should be uppercase, written as colon-separated hex pairs such as `14:6D:E9:...`. A lowercase value is a common reason a correct-looking file still fails verification.

## Sources

- Android Developers, [Verify Android App Links](https://developer.android.com/training/app-links/verify-android-applinks): the `autoVerify` filter, `pm get-app-links`, `pm verify-app-links --re-verify`, `pm set-app-links --package <package> 0 all`, the verification states, the 20 second and few-minute waits, the Android 11 and Android 12 host behavior, and the Android 15 re-verification delay.
- Android Developers, [Troubleshoot App Links](https://developer.android.com/training/app-links/troubleshoot): no redirects, uppercase values, the trailing-period host, the Play App Signing certificate, `autoVerify` on each filter, and `am start --debug-link` on Android 17 and later.
- Android Developers, [Declare website associations](https://developer.android.com/training/app-links/configure-assetlinks): file location, `application/json`, multiple certificates, multiple apps and the Play Console signing snippet.
- Google, [Digital Asset Links getting started](https://developers.google.com/digital-asset-links/v1/getting-started) and the [statements.list reference](https://developers.google.com/digital-asset-links/reference/rest/v1/statements/list): the statement format and the API that reason 12 queries.

## Related Guides

- **Start here:** [What Is Deep Linking? How Mobile Deep Links Work on iOS and Android](https://warplink.app/blog/deep-linking-guide) covers how App Links, Universal Links, and deferred deep links fit together, including the [App Links section](https://warplink.app/blog/deep-linking-guide#app-links-android).
- **The iOS sister checklists:** [Universal Links Not Working? 15 Causes and the Fix for Each](https://warplink.app/blog/universal-links-not-opening) and [Universal Links Open Safari? Fix Your apple-app-site-association File](https://warplink.app/blog/apple-app-site-association-not-working).
- **Test it properly:** [How to Test Deep Links: simctl and adb Commands, Plus a Pre-Launch Checklist](https://warplink.app/blog/how-to-test-deep-links) covers `adb shell pm get-app-links`, `pm verify-app-links`, and the rest of the verification commands in one place.
- **After the link opens:** [10 Deep Linking Mistakes That Break Install Attribution](https://warplink.app/blog/deep-linking-mistakes-that-break-install-attribution).
- **Migrating off Firebase Dynamic Links:** the [Firebase Dynamic Links Migration Guide](https://warplink.app/blog/firebase-dynamic-links-migration-guide) includes the assetlinks.json and intent-filter setup as part of the cutover, and [Firebase Dynamic Links alternatives compared](https://warplink.app/blog/firebase-dynamic-links-alternative) weighs your replacement options.
- **Reference:** the [Android deep links docs](https://warplink.app/docs/sdks/android/deep-links), the [deep linking concepts page](https://warplink.app/docs/concepts/deep-linking), and the [pricing page](https://warplink.app/pricing).

Once your App Links verify and open the right screen, the next question is attribution: which link, campaign, or channel drove the open and the install, and how those opens show up in your analytics. That is where [WarpLink](https://warplink.app/docs/concepts/attribution) picks up after the link resolves.

Check the file with the [free assetlinks.json validator](https://warplink.app/tools/assetlinks-validator), then [let WarpLink host and update this file](https://dash.warplink.app/auth/signup) so a key or package change is one edit.
