Deeplinkly

Glossary/Implementation artifacts

Android App Link

Definition

An Android App Link is an HTTPS URL that opens an Android app directly, without a chooser dialog, because the system has verified through a file on the domain that the app is authorised to handle it.

It is the Android counterpart to an iOS Universal Link, and the same idea implemented with different parts: an intent filter marked autoVerify on the app side, and an assetlinks.json file on the domain side. The word "verified" is what separates it from an ordinary web intent, and since Android 12 it is also what separates a link that opens your app from one that opens Chrome.

The two halves

Both declarations have to name each other, and both are static. Android resolves the pairing at install time, in the background, with no user involvement.

AndroidManifest.xml — the app's claim
<activity
    android:name=".DeepLinkActivity"
    android:exported="true"
    android:launchMode="singleTask">

  <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>
/.well-known/assetlinks.json — the domain's authorisation
[
  {
    "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"
      ]
    }
  }
]

Every declared host needs its own file

example.com and www.example.com are separate hosts, verified separately. Declaring both in the manifest while hosting the file on only one gives you a domain that half works — and the half that fails is usually the one your marketing links actually use.

App Link, deep link, or web intent

Android's own documentation uses three overlapping terms, and the distinction decides whether the user sees a dialog, the browser, or your app.

The three link types Android distinguishes.
Deep linkWeb linkApp Link
SchemeAny, including myapp://http/httpshttps only
autoVerifyNoNoYes
Domain ownership provedNoNoYes, via assetlinks.json
Android 12+ behaviourChooser, or the user's defaultOpens the browserOpens the app directly
Hijackable by another appYesYesNo
Works if the app is missingNoYes — web pageYes — web page

Android 12 removed the safety net

Before Android 12, an unverified HTTPS link still reached the app because the system offered a chooser. From Android 12 it goes straight to the browser. Nothing about your app changed — the tolerance for failed verification did, which is why so many long-standing setups appeared to break at once.

Checking the real state

Do not infer verification from behaviour. Read it, in one command, and let the state name the half of the system to investigate.

Verification state and forced re-check
# Android 12+: per-host verification state
adb shell pm get-app-links com.example.shop

# Force a fresh attempt after fixing the domain
adb shell pm verify-app-links --re-verify com.example.shop

# Simulate the browser's tap
adb shell am start -W -a android.intent.action.VIEW \
  -c android.intent.category.BROWSABLE \
  -d "https://example.com/products/42"

# Android 11 and earlier
adb shell dumpsys package domain-preferred-apps
Where to look next, by verification state.
StateInvestigate
verifiedRouting code — the link is reaching the app
noneWhether verification ran at all; re-verify and check network at install
legacy_failureThe domain: fingerprint, redirect, content type
Only under User selectionVerification is failing; the user approved it manually

The causes, in the order they actually occur: the fingerprint is the upload key rather than the Play app signing key; the file is behind a redirect; the content type is wrong; a custom scheme is sitting in the autoVerify filter; the debug build carries an applicationIdSuffix that no statement covers.

Receiving the link

Verification only decides that the intent reaches your activity. Reading it is still your job, and with singleTask the intent arrives at two different callbacks depending on whether the app was already running.

DeepLinkActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    handle(intent)
}

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    setIntent(intent)
    handle(intent)
}

private fun handle(intent: Intent) {
    val uri = intent.data ?: return
    val campaign = uri.getQueryParameter("utm_campaign")
    when (uri.pathSegments.firstOrNull()) {
        "products" -> openProduct(uri.pathSegments.getOrNull(1), campaign)
        else       -> openHome()
    }
}

The full URL including query string is on intent.data, so campaign parameters survive the hop into the app. What does not survive is the case where the app was not installed when the link was tapped — that is deferred deep linking, and it needs the Play Install Referrer rather than an intent.

Deep link debugger

Enter your domain and package name and it fetches /.well-known/assetlinks.json exactly as Android's verifier does — checking redirects, content type, relation string, package name and fingerprints — and reports which step fails, so you know whether to fix the domain or the manifest.

Open the deep link debugger

Frequently asked questions

What is the difference between an Android App Link and a deep link?
A deep link is any URL that opens content in an app, including unverified custom schemes. An Android App Link is specifically an HTTPS link whose domain has been verified through assetlinks.json, so the system routes it to the app with no chooser dialog and no possibility of another app intercepting it. All App Links are deep links; most deep links are not App Links.
Do Android App Links require assetlinks.json?
Yes. Verification is exactly the check that the domain publishes a Digital Asset Links statement naming your package and matching your app's signing certificate fingerprint. Without the file the intent filter still resolves, but as an unverified web link — which on Android 12 and later means it opens the browser instead of your app.
Why do my App Links open Chrome instead of my app?
Verification has failed. Run adb shell pm get-app-links against your package: legacy_failure points at the domain, usually a fingerprint from the upload key rather than the Play app signing key, a redirect in front of the file, or a wrong content type. A state of none means the check never completed, often because the device had no network connection at install time.
Can I use Android App Links without publishing to Google Play?
Yes. Verification only compares the app's signing certificate against the fingerprints in assetlinks.json, so a directly distributed APK works as long as the fingerprint of the key that signed it is listed. The Play-specific complication is that Play App Signing re-signs your upload, so apps distributed both ways need both fingerprints in the file.
How long does App Links verification take?
Normally seconds, in the background, immediately after install. If the device is offline at that moment the state stays none and Android retries with an increasing backoff rather than at once. Running adb shell pm verify-app-links --re-verify forces an immediate attempt, which is the fastest way to confirm a domain-side fix without reinstalling.

Related terms

  • assetlinks.jsonassetlinks.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.
  • Android App Link VerificationAndroid App Link verification is the process by which Android confirms, at install time, that a domain named in an app's intent filter publishes an assetlinks.json file authorising that app to handle its URLs.
  • Intent FilterAn intent filter is an element in an Android app's manifest that declares which intents an activity can handle, including the URL patterns that should open it.
  • Universal LinkA Universal Link is a standard HTTPS URL that opens an iOS app directly when that app is installed and the domain has authorised it, and loads the equivalent web page when it is not.
  • Android App Links not workingAndroid 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.