Android Deep Linking: App Links and Deferred Matching (2026)

Android Deep Linking: App Links and Deferred Matching (2026)

Key takeaways

  • Android App Links are verified through a Digital Asset Links handshake between an assetlinks.json file on your domain and an intent filter marked autoVerify in your manifest.
  • Unlike plain deep links, verified App Links skip the app-chooser dialog and cannot be hijacked by another app claiming the same scheme.
  • Deferred deep linking on Android has a real advantage over iOS: the Play Install Referrer API gives a deterministic, free match with no permission prompt, which should be your primary method before falling back to fingerprinting.

Deep links vs verified App Links on Android

Android has supported basic deep links, URLs that open your app via an intent filter, for a long time. The problem with a plain deep link is that any app can register an intent filter for the same scheme, so Android shows the user a disambiguation dialog asking which app should handle it. App Links solve that by adding domain verification on top: once verified, your app opens the URL directly, with no dialog, and no other app can claim it.

That verification is what separates a "deep link" from an "App Link" on Android. Both use the same intent filter syntax; App Links just add the autoVerify attribute and a hosted verification file.

The Digital Asset Links handshake

Android verifies ownership through a file called assetlinks.json, hosted at exactly https://yourdomain.com/.well-known/assetlinks.json. It must be served with an application/json content type and return a direct 200 response with no redirects. A CDN or reverse proxy that redirects HTTP to HTTPS, or serves unknown extensions as text/plain, will silently break verification even though the file looks fine in a browser.

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.yourcompany.yourapp",
    "sha256_cert_fingerprints": ["YOUR_SHA256_FINGERPRINT"]
  }
}]

When android:autoVerify="true" is present on an intent filter, installing the app on Android 6.0 and higher triggers the system to query this file for every hostname in that filter's <data> elements. On Android 11 and below, verification requires a matching entry for every host in the manifest; later versions relaxed this per-host.

Configuring the intent filter

The intent filter lives on the activity that should handle the link, inside AndroidManifest.xml.

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

Do not include other unrelated schemes in the same filter, since that can prevent verification. If your links use a wildcard host, be aware that Android verifies each specific subdomain it encounters against assetlinks.json individually, not the wildcard pattern itself, so most teams find explicit hostnames simpler to reason about.

Testing App Links

A quick way to launch the activity for a given URL without testing whether the web-side verification actually succeeded:

adb shell 'am start -a android.intent.action.VIEW \
  -c android.intent.category.BROWSABLE \
  -d "https://yourdomain.com/products/123"' \
  com.yourcompany.yourapp

That command proves your app-side routing works, but it does not test the web verification. To test the full path, click the link from an actual browser, or send it to yourself through an app like Gmail or Google Docs, and confirm the link opens your app directly with no chooser dialog.

Deferred deep linking on Android

The same install-gap problem exists on Android: a user without your app taps a link, goes to the Play Store, installs, and opens the app, and nothing native tells that fresh app instance which link was originally clicked. Android has one real advantage here over iOS, though: the Play Install Referrer API.

The Play Install Referrer API lets an app read referrer data that Google Play attached at install time, which gives a deterministic, free match with no fingerprinting and no permission prompt. It should be your primary matching mechanism on Android. Fingerprint matching, comparing device signals like IP address and OS version between the click and the first open within a short time window, remains useful as a fallback for sideloaded installs or edge cases the Install Referrer API does not cover.

Integrating a deep linking SDK

Hosting assetlinks.json correctly, keeping SHA-256 fingerprints in sync across debug and release builds, and building install-referrer plus fingerprint matching yourself is a maintainable but ongoing job. A managed SDK hosts the verification file, runs both matching methods, and gives you a listener API. Add Ulinkly's Android SDK from Maven Central (implementation("ly.ulink:ulink-sdk:1.2.0")), initialize it in your Application class, then collect resolved links:

import ly.ulink.sdk.ULink
import ly.ulink.sdk.models.ULinkConfig
import ly.ulink.sdk.models.ULinkResolvedData

// 1. Initialize once in your Application class
ULink.initialize(
    context = this,
    config = ULinkConfig(apiKey = "YOUR_API_KEY")
)

// 2. Observe resolved links (Kotlin Flow) from a lifecycle scope
val ulink = ULink.getInstance()
lifecycleScope.launch {
    ulink.unifiedLinkStream.collect { data: ULinkResolvedData ->
        navController.navigate(data.slug)
    }
}

The full setup, including the dynamic-link stream and the Java (initializeAsync) API, is in the Android SDK guide.

Frequently asked questions

What is the difference between a deep link and an App Link on Android?

A plain deep link uses an intent filter but is not verified, so Android may show a chooser dialog if more than one app registers the same scheme. An App Link adds the autoVerify attribute and a hosted assetlinks.json file, so once verified, the link opens your app directly with no chooser and cannot be hijacked.

Why isn't my Android App Link opening my app directly?

Common causes include assetlinks.json not being served at exactly /.well-known/assetlinks.json, the file returning the wrong content type or a redirect, a mismatched SHA-256 signing fingerprint between your build and the file, or the intent filter missing android:autoVerify="true".

What is the Play Install Referrer API used for?

It lets a freshly installed Android app read referrer data that Google Play attached at install time, giving a deterministic match between an install and the original link click. It requires no permission prompt and no fingerprinting, which makes it the preferred method for deferred deep linking on Android.

Do I need fingerprint matching if I use the Play Install Referrer API?

You should still implement fingerprint matching as a fallback. The Install Referrer API does not cover every install path, including some sideloaded installs, so a fallback keeps your match rate high for the cases the primary method misses.

Want deferred deep linking without building the matching layer yourself?

Ulinkly hosts assetlinks.json and runs Install Referrer plus fingerprint matching for Android. Start free for up to 10,000 monthly active users at ulink.ly/pricing.

Sources

Read More Articles

Explore more guides and insights on deep linking and mobile development.

Back to Blog