guides

Android App Links setup: the complete walkthrough

the linkboo team·6 min read·updated Mon Jun 01 2026 17:00:00 GMT-0700 (Pacific Daylight Time)
On this page

scope

A complete walkthrough for setting up Android App Links from zero. Covers the assetlinks.json file, the <intent-filter android:autoVerify="true"> declaration, the Activity handlers, and the most common debugging steps when App Links fail verification or surface the "Open with" disambiguation dialog.

Cluster C hub: /guides/firebase-dynamic-links-replacement. Pair: /guides/ios-universal-links-setup.

prerequisites

  • An Android app project in Android Studio with a valid applicationId in build.gradle.
  • The release-signing keystore for your app (or, if testing in debug, the debug keystore at ~/.android/debug.keystore).
  • A domain you control with the ability to serve a static file over HTTPS and a valid TLS certificate.
  • Google Play Console access if you intend to use Play App Signing (which changes the SHA-256 fingerprint Android uses for verification).

step 1 — choose the domain

The domain that will host your App Links must:

  • Be HTTPS with a publicly trusted certificate.
  • Serve /.well-known/assetlinks.json directly, with Content-Type: application/json.
  • Be reachable from Google's verification crawler (no IP allowlists, no aggressive bot protection that blocks Mozilla/5.0 Android-DigitalAssetLinks).

The domain you choose is what users will see in HTTPS URLs. Apex (yourdomain.com) is most common. Subdomains work — each subdomain requires its own assetlinks.json file at its own /.well-known/ path.

step 2 — get your SHA-256 cert fingerprint

The assetlinks.json file binds your domain to a specific app signature. You need the SHA-256 fingerprint of the cert your app is signed with.

If signing locally

keytool -list -v -keystore /path/to/your-keystore.jks -alias your-alias

Look for the SHA256: line. Format is 14:6D:E9:83:C5:... — 64 hex characters separated by colons.

If using Play App Signing

Google re-signs your app with a Play-managed key when distributing. The fingerprint Android uses for verification is the Play-managed one, not your upload key.

In the Play Console: → Your App → Setup → App Integrity → App Signing → copy the SHA-256 certificate fingerprint.

For debug builds (Android Studio Run button on a development device):

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

For multi-environment setups (debug + release + staging), include all relevant fingerprints in the assetlinks.json. The file accepts multiple entries.

Create the file at https://yourdomain.com/.well-known/assetlinks.json. Format details: /guides/assetlinks-json-explained.

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

For multiple build variants:

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.yourcompany.yourapp",
      "sha256_cert_fingerprints": [
        "<release fingerprint>",
        "<debug fingerprint>"
      ]
    }
  }
]

Verify the file is reachable and valid:

curl -sI https://yourdomain.com/.well-known/assetlinks.json | head
# Expected:
# HTTP/2 200
# content-type: application/json

curl -s "https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://yourdomain.com&relation=delegate_permission/common.handle_all_urls"
# Should return your statement, validated by Google

The Google validation endpoint is authoritative — if it doesn't see your statement, neither will Android.

step 4 — declare the intent filter

In AndroidManifest.xml, add an intent-filter to the Activity that should handle deep links:

<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="yourdomain.com"
              android:pathPrefix="/product/" />
        <data android:scheme="https"
              android:host="yourdomain.com"
              android:pathPrefix="/profile/" />
    </intent-filter>
</activity>

Key elements:

  • android:autoVerify="true" — this is what makes the intent filter an App Link rather than a plain deep link. Without it, the user sees the disambiguation dialog ("Open with") instead of automatic routing.
  • android:exported="true" — required since API 31 (Android 12).
  • android:scheme="https" — must be HTTPS.
  • android:host="yourdomain.com" — must match the assetlinks.json hosting domain.
  • android:pathPrefix — restricts which paths the app handles. Use multiple <data> elements for multiple prefixes.

step 5 — implement the handler

In MainActivity.kt:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    handleIntent(intent)
}

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

private fun handleIntent(intent: Intent) {
    val data: Uri = intent.data ?: return
    if (data.scheme != "https" || data.host != "yourdomain.com") return
    
    val segments = data.pathSegments
    when (segments.firstOrNull()) {
        "product" -> segments.getOrNull(1)?.let { id -> navigateToProduct(id) }
        "profile" -> segments.getOrNull(1)?.let { id -> navigateToProfile(id) }
    }
}

onCreate handles cold launches (app was not running, user tapped link). onNewIntent handles warm launches (app is running, user tapped link).

step 6 — install and verify

Install a release-signed (or debug-signed, matching your fingerprint) build on a real device. Then check verification status:

adb shell pm get-app-links com.yourcompany.yourapp

Expected output:

com.yourcompany.yourapp:
    ID: ...
    Signatures: [...]
    Domain verification state:
      yourdomain.com: verified

If you see none or 1024 (legacy unverified) instead of verified:

# Force re-verification (Android 11+)
adb shell pm verify-app-links --re-verify com.yourcompany.yourapp

For Android 12+ (API 31+), the system requires the user to grant the verified-link preference in some cases:

adb shell pm set-app-links --package com.yourcompany.yourapp 0 all

This resets the user-preference state and allows verification to proceed cleanly.

step 7 — test on device

  1. From Chrome on the device, type https://yourdomain.com/product/123 and navigate.
  2. The app should open directly, not the disambiguation dialog.
  3. If the disambiguation dialog appears, verification failed — see step 8.
  4. From another app's share sheet, share the URL. The "Your App" option should appear.

For warm-launch testing, open the app, then tap a link from Mail or Messages. The app should receive the URL via onNewIntent.

step 8 — common failure modes

Symptom Likely cause
Disambiguation dialog ("Open with") appears autoVerify="true" missing or verification failed
adb shell pm get-app-links shows none assetlinks.json not reachable or fingerprint mismatch
Works on debug build but not Play Store install Play App Signing fingerprint not in assetlinks.json
Works for some paths but not others pathPrefix in intent filter doesn't cover the URL pattern
Verification fails sporadically assetlinks.json hosted behind a 301 redirect or with wrong Content-Type
Works on Android 10 but fails Android 12+ android:exported="true" missing on the Activity
Link tapped inside Instagram or TikTok opens web instead of app Expected — App Links don't trigger from inside in-app WebView. See /guides/in-app-browser-logged-out

The Play App Signing fingerprint mismatch is the most common production bug. The fingerprint you used during local testing is your upload key, but Google replaces it with the Play-managed key when distributing. Update assetlinks.json with both fingerprints, or use only the Play-managed fingerprint for the production statement.

step 9 — fallback for unverified paths

If a user has the app but verification failed (network error at install time, for instance), the disambiguation dialog appears. To bypass this for known scenarios, you can construct a web-side link that explicitly targets your app package with a web fallback URL — so if the app isn't found, the user lands on the web page instead.

step 10 — production hardening

Before Play Store release:

  • Verify all production fingerprints are in assetlinks.json (release, plus Play-managed if using Play App Signing).
  • Run pm get-app-links on a test device after installing from Play Store, not just from Android Studio.
  • Add monitoring on the assetlinks.json URL.
  • Use the Google Statement List API as a continuous validation check.

For multi-domain setups (apex + www, or production + staging), publish a separate assetlinks.json on each host. Statements don't propagate across subdomains.

For non-app linking infrastructure where App Links don't apply, the no-SDK linkboo path is at link.boo/api.

references

  • Android App Links Verification documentation
  • Google Digital Asset Links protocol specification
  • Play App Signing documentation
  • Android Manifest intent-filter reference

Stop losing the click after the tap.

linkboo escapes the in-app browser so your real page loads — fast.

Start for free →