Deep links that "sometimes open the app" are broken. That qualifier — sometimes — means the setup is partially working or intermittently failing, and you are losing users to the browser on every failure without a log entry, a crash report, or any visible signal that anything went wrong. The user sees a web page instead of the app screen they expected. Most of them do not retry. They are gone.

Android App Links — the HTTP URL-based deep linking mechanism introduced in Android 6.0 and significantly evolved through Android 12 and Android 15 — are the correct modern solution for this class of problem. When properly configured, a verified App Link opens your app instantly, on the right screen, with the correct content loaded, with no user choice required. When misconfigured, it fails silently in ways that are invisible unless you specifically test for them.

This guide is the complete implementation and testing reference for Android App Links in 2026 — covering the three-component architecture, the assetlinks.json file in full detail, every known failure mode and its fix, the fallback chain across Android versions, Dynamic App Links introduced in Android 15, the aftermath of Firebase Dynamic Links' deprecation, and the exact ADB commands and test matrix your QA team should run before every release that touches deep linking.

Android 12 Changed fallback from disambiguation dialog to browser for unverified links — a silent breaking change for thousands of apps Android Docs
Android 15 Introduced Dynamic App Links — server-side deep link rule updates without shipping a new app release Android Docs 2025
2025 Firebase Dynamic Links shut down completely — millions of links broken overnight for apps that did not migrate Google
Install time Verification occurs only at install or update time — not per-click. A broken fix requires a reinstall to take effect Android Docs

The Three Core Components of Android App Links

Android App Links require three components to work correctly. Missing or misconfiguring any one of them causes silent failures. All three must be correct simultaneously — and when they are, Android routes matching HTTP URLs directly to your app without user interaction.

ComponentWhere It LivesWhat It DoesFailure Mode
Intent FiltersAndroidManifest.xmlDeclares which HTTP URL patterns your app can handle, with android:autoVerify="true" to trigger domain verificationMissing autoVerify, overlapping path patterns across activities, or incorrect android:host value causes verification skip or disambiguation dialog
Digital Asset Links Filehttps://yourdomain.com/.well-known/assetlinks.jsonDeclares that your domain trusts your Android app (by package name and SHA-256 signing fingerprint) to handle its URLsWrong content-type, HTTP redirect, fingerprint mismatch, missing host entries, or file size over 128 KB — all cause verification failure
Verification SystemAndroid OS — fires at install/update timeFetches and validates assetlinks.json for every host in your intent filters, stores result, uses it for all subsequent link resolutionCache delays mean fixed config may not reflect on device for hours; requires reinstall to force re-verification during development

Part One: The AndroidManifest.xml Intent Filter

The intent filter in your manifest tells Android which URLs your app wants to handle. For App Links — as opposed to basic deep links — the critical element is android:autoVerify="true", which tells Android to verify domain ownership at install time rather than just presenting a disambiguation dialog.

AndroidManifest.xml — Intent Filter for App Links
<activity android:name=".MainActivity">

    <!-- Standard intent filter for launcher -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

    <!-- App Links intent filter — autoVerify triggers domain verification -->
    <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" />

        <!-- Must be https for App Links — http alone is insufficient -->
        <data
            android:scheme="https"
            android:host="example.com"
            android:pathPrefix="/product" />
    </intent-filter>

    <!-- Separate filter for www subdomain — Android treats subdomains as separate hosts -->
    <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="www.example.com"
            android:pathPrefix="/product" />
    </intent-filter>

</activity>
Critical: Android Treats Subdomains as Separate Hosts

example.com and www.example.com are different hosts to the Android verification system. If your app handles links on both, you need separate intent filters for each and an assetlinks.json entry on each. The system queries https://example.com/.well-known/assetlinks.json and https://www.example.com/.well-known/assetlinks.json independently. A host mismatch — android:host in your manifest does not exactly match a hostname verified in your assetlinks.json — is one of the most common silent failure causes.

Part Two: The assetlinks.json File — The Most Failure-Prone Component

The Digital Asset Links file is the contract between your domain and your app. It must be hosted at exactly https://yourdomain.com/.well-known/assetlinks.json, served with the correct content-type header, without any HTTP redirect, and containing the correct package name and SHA-256 certificate fingerprint.

Every one of those requirements is a potential failure point. A redirect from HTTP to HTTPS, a CDN caching the wrong content-type, or an incorrect certificate fingerprint (using the debug signing fingerprint in a production build, for example) will all cause the verification to fail — silently, with no visible error on the device, only a fallback to the browser or disambiguation dialog when the link is tapped.

assetlinks.json — Complete Structure
[{
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
        "namespace": "android_app",
        "package_name": "com.example.yourapp",

        // SHA-256 fingerprint of your PRODUCTION signing certificate
        // Get it with: keytool -list -v -keystore your-key.jks
        // Or from Google Play Console → App Integrity → App signing key certificate
        "sha256_cert_fingerprints": [
            "AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78",
            // Optional: include debug fingerprint for development testing
            "12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB:CD:EF:12:34:56:78:90:AB"
        ]
    }
},
// Android 15+: Dynamic App Links configuration (optional)
{
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
        "namespace": "android_app",
        "package_name": "com.example.yourapp",
        "sha256_cert_fingerprints": ["AB:CD:EF:..."],
        "dynamic_app_deep_link_components": [{
            "include_patterns": ["/product/*", "/profile/*"],
            "exclude_patterns": ["/product/admin"]
        }]
    }
}]

How to Get Your SHA-256 Fingerprint

There are three sources for the correct fingerprint, and using the wrong one is a very common misconfiguration error.

Three Ways to Get the Correct SHA-256 Fingerprint
# Method 1: From your keystore file (local signing)
keytool -list -v -keystore /path/to/your-release.jks -alias your-alias
# Look for: SHA256: AB:CD:EF:... in the output

# Method 2: From Google Play App Signing (if using Play-managed signing)
# Go to: Play Console → Your App → Setup → App Integrity → App signing key certificate
# Copy the SHA-256 certificate fingerprint shown there
# ⚠️  This is the ONLY correct fingerprint when using Play App Signing
# Your local keystore fingerprint will NOT match — verification will fail

# Method 3: From an installed APK (verify what's actually deployed)
apksigner verify --print-certs /path/to/your-release.apk
# Or: keytool -printcert -jarfile /path/to/your-release.apk

# Verify your assetlinks.json is live and correct:
curl -v https://yourdomain.com/.well-known/assetlinks.json
# Confirm: HTTP/2 200, Content-Type: application/json, no redirects in the chain
The Google Play App Signing Fingerprint Trap

If your app uses Google Play App Signing — which is the default for new apps since 2021 — Google re-signs your APK with a Google-managed certificate before distributing it. The SHA-256 fingerprint of that certificate is different from your upload key fingerprint. If you use your local upload key fingerprint in assetlinks.json, verification will fail for all Play Store installs. The correct fingerprint is found in Play Console → Your App → Setup → App Integrity → App signing key certificate. This is one of the most common causes of App Links working in local test builds but failing in production.

Part Three: Server and CDN Requirements for assetlinks.json

The file must be served exactly as the Android system expects it. Deviations that seem minor — a redirect, a slightly wrong content-type, a CDN caching header — fail the verification silently.

  1. Serve at exactly https://yourdomain.com/.well-known/assetlinks.json — no redirects

    The Android system fetches this path directly. If your server redirects HTTP to HTTPS, or www to non-www, the fetch fails. The file must be available on HTTPS at the canonical path without any redirect in the chain. If your CDN or server adds a redirect on /.well-known/ paths, you must add an explicit exception rule for that directory. On Cloudflare, add a Page Rule to exclude /.well-known/* from "Always Use HTTPS" redirect enforcement.

  2. Content-Type header must be application/json

    Some servers serve .json files with a text/plain content-type or no content-type at all. The Android verification system requires application/json. For Firebase Hosting, add explicit headers to your firebase.json. For Vercel, add a headers rule in next.config.js. For Nginx, add a types block or explicit add_header for the .well-known directory.

  3. File size must be under 128 KB

    If your app has many hosts and many certificates, the assetlinks.json file can grow large. The Android system has a 128 KB size limit. Files over this limit are rejected. If you are approaching this limit, use wildcards in your pathPrefix values and consolidate entries rather than listing every individual path.

  4. Ensure the file is served from the exact domain — not a CDN subdomain

    The Android system queries https://yourdomain.com/.well-known/assetlinks.json — the same domain declared in your intent filter's android:host. If your CDN routes the file from a different origin domain, or if a reverse proxy serves it from a different hostname, the verification fails. The response must come from the canonical domain as it appears in your manifest.

Firebase Hosting — firebase.json (add headers for both verification files)
{
    "hosting": {
        "headers": [{
            "source": "/.well-known/assetlinks.json",
            "headers": [{
                "key": "Content-Type",
                "value": "application/json"
            }]
        }, {
            "source": "/.well-known/apple-app-site-association",
            "headers": [{
                "key": "Content-Type",
                "value": "application/json"
            }]
        }]
    }
}

Part Four: The Fallback Chain — What Actually Happens When Verification Fails

Understanding the fallback chain is essential for writing an adequate test plan. What Android does when App Link verification fails changed significantly in Android 12, and the change has ongoing implications for apps that have not been updated or tested since.

✓ Best Case — Verified App Link
App opens directly, correct screen loaded, no dialog
Verification passed at install time. assetlinks.json is valid, fingerprint matches, host matches. User taps link → app opens to the correct content immediately. This is the state your implementation must achieve on all declared hosts.
⚠ Pre-Android 12 Fallback — Disambiguation Dialog
Android 6–11: User shown a chooser dialog
Verification failed or no autoVerify="true". Android presents a dialog: "Open with [Your App] or [Browser]." Most users choose the browser or dismiss the dialog. Conversion rate from this state is very low. Still the behaviour on Android 6–11 when verification is absent.
✕ Android 12+ Fallback — Browser Opens Directly
Android 12 and later: Link opens in the browser with no prompt
Starting Android 12 (API level 31), an unverified App Link resolves directly to the browser — no disambiguation dialog, no chance for the user to choose your app. This is a silent failure with zero user-facing indication that anything went wrong with the configuration. The user simply sees a web page.
The Android 12 Change That Broke Millions of Links

Before Android 12, a misconfigured App Link still gave the user a chance to choose your app via the disambiguation dialog. Many apps had broken or partially configured App Links that still "worked" because users would select the app from the dialog. Android 12 removed this fallback: broken verification now routes directly to the browser, with no dialog. Apps that had been living on the disambiguation dialog as an unofficial fallback for broken App Links saw a sudden, unexplained drop in app opens from deep links at the Android 12 rollout — not from an app change, but from an OS change to how fallbacks work.

Part Five: Dynamic App Links — Android 15's Server-Side Control

Android 15 introduced Dynamic App Links, one of the most significant changes to the App Links system since its introduction. Previously, assetlinks.json was used primarily for basic domain verification. With Dynamic App Links, it becomes a configuration file that can update your app's deep link behaviour without requiring a new app release.

Android devices with Google services periodically refresh your assetlinks.json file and apply new deep link rules dynamically. This means you can add, modify, or exclude URL patterns server-side — and those changes propagate to user devices without requiring them to update the app.

Dynamic App Links — dynamic_app_deep_link_components configuration (Android 15+)
[{
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
        "namespace": "android_app",
        "package_name": "com.example.yourapp",
        "sha256_cert_fingerprints": ["AB:CD:EF:..."],
        "dynamic_app_deep_link_components": [{
            // Paths that SHOULD open the app
            "include_patterns": [
                "/product/*",
                "/profile/*",
                "/order/*"
            ],
            // Paths that should NEVER open the app even if they match
            "exclude_patterns": [
                "/product/admin",
                "/profile/settings/delete"
            ]
        }]
    }
}]

Practical applications of Dynamic App Links include: immediately excluding a path from App Link handling when a feature is sunset (without waiting for a release cycle), adding new product categories or sections to App Link coverage as they launch, and A/B testing deep link destinations by updating server-side routing rules. The dynamic refresh is not instantaneous — devices periodically fetch the file based on their own schedules — so Dynamic App Links are appropriate for non-time-critical configuration changes, not real-time routing decisions.

Part Six: Firebase Dynamic Links Is Dead — What to Do Now

Firebase Dynamic Links was deprecated in August 2023 and shut down completely in 2025. Any app still using Firebase Dynamic Links has broken deep links — affected users are experiencing failures silently, often without the development team knowing because the links resolve without crashing the app.

If You Are Still Using Firebase Dynamic Links — Read This First

Firebase Dynamic Links links no longer work as of their shutdown date in 2025. If your app's deep linking, sharing functionality, or email campaigns use page.link or Firebase short link domains, those links are now broken or producing degraded experiences. Users who click them may see an error page, a generic web fallback, or nothing at all.

Migration requires: auditing all Firebase Dynamic Links in production; designing equivalent URL patterns using your own domain; configuring App Links intent filters and assetlinks.json; updating link generation in your app code and any email, SMS, or social campaigns; testing all user journeys end-to-end; and monitoring conversion rates during the transition period to catch silent regressions.

Part Seven: The Complete Test Matrix

Deep link behaviour varies across Android versions, install states, entry points, and browser environments. Testing only the happy path — link tapped on a device where the app is installed and verification passed — covers one of the roughly twelve distinct scenarios that affect users in the wild. Here is the complete matrix.

ADB Commands for Development Testing

ADB Test Commands — Run These During Development
# Test a specific App Link URL (replace with your domain and path)
adb shell am start -W -a android.intent.action.VIEW \
    -d "https://yourdomain.com/product/123" com.yourpackage.name

# Expected: App launches directly to product screen
# Failure: Browser opens or disambiguation dialog appears

# Verify the assetlinks.json file is correctly served and parseable
curl -v https://yourdomain.com/.well-known/assetlinks.json
# Confirm: HTTP/2 200 OK, Content-Type: application/json, no redirects

# Check App Link verification status on a connected device (Android Studio)
# Tools → App Links Assistant → Verify Asset Links

# Force re-verification after changing assetlinks.json (development only)
# Uninstall the app, then reinstall — verification only runs at install/update
adb uninstall com.yourpackage.name
# Reinstall via Android Studio or adb install

# Check Android's verification log for your app
adb shell pm get-app-links --user cur com.yourpackage.name
# Look for: "verified" = true for each host

# Test fallback: what happens when the app is NOT installed
adb uninstall com.yourpackage.name
adb shell am start -a android.intent.action.VIEW -d "https://yourdomain.com/product/123"
# Expected: Opens in browser — confirm your web fallback page is correct

Full Test Matrix

Test ScenarioAndroid VersionExpected BehaviourHow to Test
Verified link, app installed, user tapsAndroid 6+App opens, correct screenADB command; Chrome URL bar; WhatsApp/SMS message link
Verified link, app not installedAllBrowser opens — check web fallback page is correct and mobile-optimisedUninstall app; tap link
Unverified link (assetlinks broken), app installedAndroid 6–11Disambiguation dialog shownBreak assetlinks.json temporarily; reinstall; tap link
Unverified link (assetlinks broken), app installedAndroid 12+Browser opens, no dialog — silent failureSame as above on Android 12+ device
Link from Gmail (app on Android)AllGmail may wrap link — may open browser instead of appEmail yourself the link; tap from Gmail
Link from WhatsAppAllWhatsApp taps usually respect App LinksSend link to yourself in WhatsApp; tap
Link via email marketing (HubSpot, Mailchimp)AllTracking wrapper rewrites URL — App Link will not trigger. Browser opensSend a tracked campaign email; tap link from Gmail/Android mail
Link via bit.ly or generic shortenerAllRedirect domain is not your verified domain. Browser always opensCreate a bit.ly link to your App Link URL; tap
Link opened inside Chrome Custom TabAllChrome Custom Tabs support App Link interception in most casesTest from apps that use CCT (Twitter, LinkedIn)
JavaScript redirect to App Link URLAllJavaScript redirects are not user-initiated — App Link interception skippedBuild test page with window.location.href redirect; test on device
QR code scan → App Link URLAndroid 9+QR scan via camera app triggers App Links correctlyPrint or display QR code; scan with default camera app
Path not declared in manifestAllBrowser opens — intent filter must cover the pathTest a URL path not in your pathPrefix list

The Complete Failure Mode Checklist

  • Wrong SHA-256 fingerprint — most common cause of production failure. Using the local keystore fingerprint when the app is distributed via Play App Signing. The correct fingerprint is in Play Console → App Integrity, not in your local .jks file. Verification works in local debug builds and fails in production. Fix: update assetlinks.json with the Play App Signing certificate fingerprint.

  • HTTP redirect on the assetlinks.json path. The most common CDN issue. Cloudflare's "Always Use HTTPS" and similar redirect rules intercept the /.well-known/ path. Android's verification system does not follow redirects. Fix: add a Page Rule or routing exception that serves /.well-known/assetlinks.json directly from origin without redirect.

  • Wrong Content-Type header on assetlinks.json. Servers serving .json files as text/plain cause verification failure. Add explicit content-type configuration to your web server, CDN, or hosting platform for the /.well-known/ directory.

  • Missing host entries in assetlinks.json. If your manifest declares android:host="example.com" and android:host="www.example.com", both must have their own assetlinks.json at their respective paths. Android verifies each host independently.

  • Overlapping intent-filter paths across activities. If two activities in your app both have intent filters that match the same URL pattern, Android may show a disambiguation dialog or fail to determine a single handler. Intent filters across activities must not overlap. The Android Docs troubleshooting guide identifies this as a common disambiguation dialog trigger even when App Links are otherwise correctly configured.

  • Not reinstalling after fixing verification config during development. Android verification runs only at install or update time. Changes to assetlinks.json or the manifest do not take effect until the app is reinstalled. Uninstall and reinstall — do not simply rebuild and run — after making any verification-related change. Server-side cache may add additional delay; retry after a few hours if the issue persists after reinstall.

  • Email marketing tracking wrappers. SendGrid, HubSpot, Mailchimp, and similar platforms wrap all tracked links through their own redirect domains. A link to https://yourapp.com/product/123 becomes https://click.sendgrid.net/wf/click?upn=.... Android only trusts your verified domain. Tracking-wrapped links will always open in the browser — not the app. This is expected and by design, not a bug in your setup.

"Deep links that sometimes open the app are not intermittently working. They are systematically broken in the scenarios you have not tested."

Frequently Asked Questions

What changed with Android App Links in Android 12?

Android 12 (API level 31) changed the fallback behaviour for unverified App Links. Before Android 12, a link that your app declared in its intent filters but could not verify would show a disambiguation dialog — giving users a chance to choose your app. Starting Android 12, unverified web links resolve directly to the browser with no dialog. Apps that relied on the disambiguation dialog as a fallback for broken verification saw a sudden unexplained drop in app opens from links at the Android 12 rollout.

What are Dynamic App Links introduced in Android 15?

Dynamic App Links, introduced in Android 15, allow app developers to update deep linking rules server-side via assetlinks.json without releasing a new app version. You can add, modify, or exclude URL patterns using the dynamic_app_deep_link_components field. Android devices with Google services periodically refresh the file and apply new rules automatically. This enables scenarios like adding new sections to App Link coverage at launch, excluding deprecated paths from app handling, and managing deep link routing without requiring app updates.

Why does my App Link work in debug but fail in production?

The most common cause is a SHA-256 fingerprint mismatch. If your app uses Google Play App Signing, the certificate used to sign the distributed APK is Google's App Signing certificate — not your upload key. Your assetlinks.json must contain the App Signing certificate fingerprint from Play Console → Setup → App Integrity, not the fingerprint from your local .jks file. Debug builds are signed with a different certificate, so a debug fingerprint in assetlinks.json works for debug installs but fails for production Play Store installs.

Do URL shorteners break Android App Links?

Yes — generic shorteners break App Links by design. Android App Links verification is domain-specific: only your verified domain triggers your app. When a user clicks a bit.ly link that redirects to your App Link URL, the OS intercepts the bit.ly domain, not your domain. Since bit.ly is not your verified domain, the link opens in the browser. The same applies to any shortener domain that is not your own verified domain. If you need short links that preserve App Link behaviour, the destination URL must eventually be your verified domain and the redirect must be a server-side redirect without a JavaScript or client-side hop that prevents the OS from resolving the final URL at the system level.

How do I force Android to re-verify App Links on a test device?

Uninstall and reinstall the app. Verification only runs at install or update time. After reinstalling, run adb shell pm get-app-links --user cur com.yourpackage.name to check verification status. Note that server-side CDN caches may delay the delivery of an updated assetlinks.json file even after reinstalling. If verification still fails after reinstall, wait a few hours for the cache to expire and try again. In Android Studio, the App Links Assistant (Tools → App Links Assistant → Verify Asset Links) runs on-device verification and shows the result without requiring a manual reinstall.

Muhammad Umar Ali
Content Strategist, Trimrly

Muhammad writes about QR code strategy, local business marketing, and practical digital tools for small business owners. He has covered Google review optimisation, print-to-digital conversion strategies, and the operational mechanics of reputation management since 2022.