# Play Console says domain ownership not verified? How we fixed Android App Links

> Gigasonet blog. By Kirtan Patel, 3 Nov 2026. Canonical: https://www.gigasonet.com/blog/android-app-links-domain-ownership-not-verified

Our invite links kept opening the browser instead of our app. Two hidden mistakes caused it, one file, one wrong key. Here is the exact checklist, commands included, that turned Play Console green.

## In 30 seconds
- Play Console showed "1 domain failed validation", so our invite links opened the app picker or the browser instead of our app.
- Mistake one: /.well-known/assetlinks.json returned a 404 page served as HTML, which fails both the file check and the content type check.
- Mistake two: we listed the upload key's SHA-256. With Play App Signing, users install a build signed with a different key, so validation still failed.
- The fix was the app signing key's fingerprint, served as application/json, plus about an hour for Google's cache to refresh.
- Fingerprints are public information. Listing them in a public file is safe and expected.

We shipped invite links for **Crewzy**, our friends photo widget app, in the format `https://www.gigasonet.com/apps/crewzy/invite?u=username`. The idea was simple: tap the link, land in the app.

Instead, Android showed an app picker, and some phones opened the browser. Play Console's Deep links page had a red badge that read **"Domain ownership not verified"**. Here is what was wrong, in the order we found it, so you can skip the guesswork.

## The pain point: links that should open your app open a browser

Android App Links only open your app directly when Android can prove that your website trusts your app. It checks this by downloading one file from your domain:

```
https://your-domain.com/.well-known/assetlinks.json
```

If that file is missing, malformed or lists the wrong signing key, Android quietly falls back to the old behaviour. Users see a chooser, or a web page, and the smooth invite flow you designed is gone.

## Step 1: check the file the way Google does

Don't guess. Ask Google's own Digital Asset Links API what it sees:

```
curl "https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://your-domain.com&relation=delegate_permission/common.handle_all_urls"
```

Ours came back with `ERROR_CODE_FETCH_ERROR` and a 404. The file simply did not exist on the live site. Worse, the 404 was our normal HTML error page, so Play Console flagged **two** failures: the JSON file check and the "content type must be application/json" check. One missing file, two red messages.

## Step 2: serve the file, as real JSON

Our site runs on Next.js, so the file goes in `public/.well-known/assetlinks.json`:

```json
[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.yourapp",
      "sha256_cert_fingerprints": ["AA:BB:CC:..."]
    }
  }
]
```

We also set the header explicitly in `next.config.ts`, so no CDN or framework default could ever serve it as anything else:

```ts
{ source: "/.well-known/assetlinks.json", headers: [{ key: "Content-Type", value: "application/json" }] }
```

Check three things after you deploy: it returns **200**, with **no redirect** on the exact host in your manifest, and the content type is **application/json**. If your bare domain redirects to `www`, the manifest host and the file must both be the `www` version.

## Step 3: the trap that cost us a day, the wrong SHA-256

We deployed, Google's API happily returned our statement, and Play Console still said **"Assetlinks JSON file failed to validate."**

The reason: we had pasted the fingerprint of our **upload key**. With Play App Signing, which is the default for new apps, you sign your bundle with the upload key, then Google re-signs it with the **app signing key** before it reaches users. Android checks the installed app's signature, so the file has to list the app signing key.

You can prove which key you used by reading the certificate inside your own bundle. No password is needed:

```
keytool -printcert -jarfile app-release.aab
```

If that SHA-256 matches what is in your file, you listed the upload key. Get the right one from **Play Console → Test and release → App integrity → App signing**, under **App signing key certificate**. The Deep links page in Play Console also shows a ready-made JSON snippet with the expected fingerprint.

We kept both fingerprints in the file. The app signing key covers users who install from Play, and the upload key covers builds we sign and install ourselves for testing.

## Step 4: wait out the cache, then verify

Google's statement API told us it caches results for about an hour (`maxAge` of 3600 seconds), so the first check after our fix still showed the old file. Once the cache refreshed, everything lined up:

```
curl "https://digitalassetlinks.googleapis.com/v1/assetlinks:check?source.web.site=https://your-domain.com&relation=delegate_permission/common.handle_all_urls&target.android_app.package_name=com.example.yourapp&target.android_app.certificate.sha256_fingerprint=AA:BB:CC:..."
```

That returns `"linked": true` when the pairing is right. On a test phone with the app installed, force a re-check and read the result:

```
adb shell pm verify-app-links --re-verify com.example.yourapp
adb shell pm get-app-links com.example.yourapp
```

You want to see your domain marked **verified**.

## The result

After the right fingerprint went live, Play Console's Deep links page switched to **"All links working"**, the domain showed **"No issues found"**, and our invite path showed **"Deep linked"**. On a test device, the invite link opened Crewzy directly, with no picker.

One more thing: users only get the new verification when they install or update the app, so ship a fresh release after the domain is green. The same steps apply to any [Android app](/services/mobile-app-development) that opens links from its own website.

## Is it safe to publish your SHA-256 fingerprints?

Yes. A fingerprint is a hash of your app's **public** certificate, and anyone can read it from your published app. It cannot sign anything. Signing needs the private key, which stays in your keystore or with Google. Never put the keystore, its passwords or service config files in a public folder. The fingerprint and package name are all the file needs.

## Your checklist

1. The file is reachable at `https://<exact host>/.well-known/assetlinks.json` with a 200 and no redirect.
2. It is served as `application/json`.
3. `package_name` matches your app exactly.
4. It lists the **app signing key** fingerprint from Play Console, plus the upload key if you test locally signed builds.
5. Your manifest's intent filter has `android:autoVerify="true"` and the same host.
6. Google's `assetlinks:check` returns `"linked": true`.
7. Recheck in Play Console, then ship an update.

## FAQ

### Why does Play Console say "JSON content type failed"?

Your server returned the file with a type other than application/json. Very often the file is missing and the server returns its HTML 404 page instead, which fails both checks at once.

### Which SHA-256 goes in assetlinks.json?

The app signing key certificate fingerprint shown in Play Console under App integrity. Add the upload key as well only if you also install builds that you sign yourself.

### How long does verification take after fixing the file?

Google's statement API caches results for about an hour. Users get the new verification when they install or update the app, so publish a new release after the fix.

### Is it safe to put certificate fingerprints in a public file?

Yes. A fingerprint is derived from your public certificate and cannot be used to sign an app. Keep the keystore and passwords private.

## Sources

- [Verify Android App Links, Android Developers](https://developer.android.com/training/app-links/verify-android-applinks)
- [Verify and maintain deep links, Play Console Help](https://support.google.com/googleplay/android-developer/answer/12463044)
- [Crewzy on Gigasonet](https://www.gigasonet.com/apps/crewzy)
