When an App Link opens the browser, it almost always means Android tried to verify your domain and the verification did not pass, so it treated your HTTPS URL as an ordinary web link and sent it to the default browser. Verification is a quiet background step, so it fails quietly too. Nothing throws. You just get Chrome. This walks the causes from most to least common, starting with the one command that tells you which camp you are in.

First, ask Android what it thinks

Before changing anything, read the verification state Android records per domain:

adb shell pm get-app-links com.example.app

That prints each host your app declares and its state. The values you can see:

  • verified · Android trusted your assetlinks.json. Links should open the app.
  • none · not verified yet. Usually a missing autoVerify, or verification has not run.
  • approved / denied · force-approved or force-denied, typically by a shell command.
  • migrated / restored · carried over from legacy verification or a data restore.
  • legacy_failure · rejected by the legacy verifier.
  • system_configured · approved automatically by the device configuration.
  • 1024 or greater · a custom, device-specific verifier error. 1026 is common in the wild, but its precise meaning is a community interpretation, not an officially documented code.

If this says verified but links still open the browser, the problem is on the device (remembered defaults, an in-app browser), not your file. Anything else points at the file or the manifest. The commands cheatsheet keeps these one click away.

Cause 1: the manifest never opted in

Verification only runs if the intent filter asks for it. The filter needs android:autoVerify="true", the VIEW action, and both the DEFAULT and BROWSABLE categories. Missing BROWSABLE and it is not web-openable; missing autoVerify and the domain stays at none:

<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" />
</intent-filter>

The Intent-Filter Generator produces this block for any URL pattern and picks the correct path attribute.

Cause 2: the assetlinks.json file is not served correctly

This is the big one. Android fetches the file from https://example.com/.well-known/assetlinks.json and is strict about how it is served. Per Google's documentation:

  • "The assetlinks.json file is served with content-type application/json."
  • "The assetlinks.json file must be accessible over an HTTPS connection, regardless of whether your app's intent filters declare HTTPS as the data scheme."
  • "The assetlinks.json file must be accessible without any redirects (no 301 or 302 redirects)."

The redirect rule trips people most. A CDN that upgrades http to https, adds a trailing slash, or rewrites /.well-known/ returns a 301 that the browser follows, so the file looks fine to you while Android records a failure. The content type bites too: a host serving it as text/plain fails even with perfect JSON. Check both:

curl -sSI https://example.com/.well-known/assetlinks.json

You want a single 200, no Location: redirect, and content-type: application/json. If curl shows a 301 or 302, fix routing first.

Cause 3: the fingerprint does not match the installed build

A minimal valid file:

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

Two fields cause most mismatches. package_name must be the exact applicationId of the installed build, easy to get wrong with a .debug suffix or a flavor. And sha256_cert_fingerprints must match the certificate that signed the installed APK. With Play App Signing, that is the key Google holds, not your upload key, so use the Play App Signing fingerprint from the Play Console. A domain can list multiple fingerprints, so add both the upload key and the Play key while testing.

Cause 4: verification has not re-run since you fixed it

Verification happens at install time, so editing the hosted file changes nothing until Android checks again. Force a clean check by resetting the state and re-verifying, or just reinstall:

adb shell pm set-app-links --package com.example.app 0 all
adb shell pm verify-app-links --re-verify com.example.app

Wait a few seconds, then run pm get-app-links again. If the domain flips to verified, you are done. If not, go back to causes 2 and 3.

Cause 5: it is verified, but the device routes around you

If pm get-app-links says verified and the link still opens elsewhere, something on the device is intercepting the tap:

  • An in-app browser. Links tapped inside Instagram, Slack threads, or some email clients open in the app's own web view and never reach the system. Test from a plain SMS, a notes app, or a QR code.
  • A remembered "open in browser" default. The app's "Open by default" screen can have supported links toggled off, which sends them to the browser regardless of verification.

To rule the device out, send the OS the intent directly, which the adb deep link testing guide covers.

The short checklist

  • Run pm get-app-links and read the state. It tells you whether this is a file or a device problem.
  • Manifest has autoVerify="true", VIEW, DEFAULT, BROWSABLE.
  • assetlinks.json is at /.well-known/, HTTPS, application/json, no redirects.
  • package_name matches the installed applicationId; fingerprint matches the signing key that reached the device.
  • Reset and re-verify, or reinstall, after any change.
  • Confirm with a real tap from a non-intercepting source, like the QR code tester.

Keep reading

Official docs


Last updated · June 2026 · by Belchior, mobile engineer