Android App Links

Android App Links Without Firebase

App Links never needed Firebase. They are an Android platform feature: the system checks a JSON file on your domain against your app's signing certificate, and if they match, your links open your app. Firebase Dynamic Links was a convenience layer on top, and when Google shut it down in August 2025 the platform capability underneath was untouched. This is the setup, the one mistake that breaks most implementations, and how to debug it properly.

Published 2026-08-26 · Last updated 2026-08-26

Verification is no longer optional

Before Android 12, an unverified web link usually produced a disambiguation dialog — annoying, but your app was at least an option. From Android 12 onward, apps targeting API 31 or later do not get that dialog. An unverified link simply opens in the browser. Verification used to be the polish on top of a working setup. Now it is the thing that decides whether your links work at all, which is why the fingerprint section below matters more than any other part of this page.

The mistake that breaks most setups

Android compares the certificate that signed the installed app against the fingerprints in your assetlinks.json. With Play App Signing — the default for new apps — the certificate that ends up on user devices is the one Google holds, not your upload key.

Why this is so easy to miss

Your local debug build verifies fine, because it is signed with the debug keystore whose fingerprint you happened to paste in. Then the Play build fails for everyone — same file, same manifest, different signing certificate. Nothing in the tooling tells you which one it expected.

Where to get the right one

Play Console → Release Setup App Integrity. Copy the SHA-256 under the app signing key, not the upload key.

For a locally-signed keystore:

keytool -list -v -keystore my-release-key.keystore -alias my-alias

List more than one

sha256_cert_fingerprints is an array. Include the Play App Signing certificate alongside your debug and internal distribution certificates so verification succeeds for both developer builds and store builds from a single file. DeepTap accepts multiple fingerprints per domain for exactly this reason.

The file itself

Serve it at https://yourdomain.com/.well-known/assetlinks.json over HTTPS, with a JSON content type, no redirects, and no authentication.

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.yourcompany.yourapp",
    "sha256_cert_fingerprints": [
      "AA:BB:CC:...:99",   // Play App Signing certificate
      "11:22:33:...:FF"    // debug / internal distribution
    ]
  }
}]

Note it is a JSON array at the top level, not an object — a small difference from the iOS file that trips people up when they write both by hand.

Declaring it in your manifest

<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="myapp.deeptap.io" />
</intent-filter>

Then read the incoming URL where your app starts:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    intent?.data?.let { Router.handle(it) }
}

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    intent.data?.let { Router.handle(it) }
}

Handling both matters: onCreate covers a cold start, onNewIntent covers a link tapped while your app is already running. Missing the second is a common "works the first time only" bug.

Debugging verification properly

Do not tap links and guess. Android will tell you the verification state directly:

# Verification state for every host your app declares
adb shell pm get-app-links com.yourcompany.yourapp

# Force another verification attempt
adb shell pm verify-app-links --re-verify com.yourcompany.yourapp

# Simulate a link tap
adb shell am start -a android.intent.action.VIEW \
  -d "https://myapp.deeptap.io/product/123" com.yourcompany.yourapp

If a host comes back as anything other than verified, the cause is almost always one of three things: the wrong fingerprint, the file not reachable at the exact path, or a redirect in front of it. Check the file first:

curl -sSI https://yourdomain.com/.well-known/assetlinks.json
# Want a 200 and a JSON content type. Any 3xx is a failure.

What about users who don't have the app?

App Links only route people who already have your app. Getting a new installer to the screen they originally tapped is deferred deep linking, and it is the one piece the platform does not give you.

DeepTap stores the destination when the click happens, sends the person to Play, and returns the saved path and query parameters through a single HTTPS GET on first launch — about fifteen lines with OkHttp, no library added to your Gradle file. Matching uses a server-side fingerprint of the client IP and the platform within a short window after the click.

Being straight about the limits: that match is most reliable when the click and the first launch happen on the same network, and it degrades behind carrier-grade NAT, a VPN, or a network change in between. When it misses, your app still opens — just on the default screen. Design that fallback deliberately rather than assuming a hit.

Working Kotlin is in the Android guide.

Hosting it without the busywork

Everything above works on your own infrastructure and there is nothing wrong with doing it that way. What DeepTap takes over is the part that keeps needing attention: serving the file at the exact path with the right headers and certificate, through every deploy, plus the same for iOS's apple-app-site-association.

Pick a subdomain like myapp.deeptap.io, enter your package name and fingerprints — as many as you need — and it is served from then on. No DNS on the default plan, no SDK, no Firebase project. From $5.99 a month with a 7-day trial.

Frequently asked questions

Do Android App Links require Firebase at all?

No, and they never did. App Links are an Android platform feature. The system verifies a JSON file called assetlinks.json on your domain against your app’s signing certificate, and nothing about that involves Firebase. Firebase Dynamic Links was a convenience layer on top; when Google shut it down in August 2025 the underlying platform capability was unaffected.

Which SHA-256 fingerprint do I put in assetlinks.json?

If you use Play App Signing — which is now the default — it must be the app signing certificate that Google holds, not your upload key and not your debug keystore. Find it in Play Console under Release, Setup, App Integrity. Using the upload key here is the single most common reason verification fails, because everything looks correct locally and only breaks for builds distributed through Play.

Can I list more than one fingerprint?

Yes, and you usually should. sha256_cert_fingerprints is an array, so you can include the Play App Signing certificate alongside your debug and internal-distribution certificates. That lets verification succeed for developer builds and Play builds from the same file.

What changed for App Links in Android 12?

Before Android 12, an unverified web link would typically show a disambiguation dialog asking which app should open it. From Android 12 onward, apps targeting API 31 or later no longer get that dialog for unverified links — the system just opens them in the browser. Verification stopped being an optimisation and became the thing that decides whether your links work at all.

How do I check whether verification actually succeeded?

Run adb shell pm get-app-links <your.package.name> on a connected device. It prints each declared host and its verification state. If a host shows anything other than verified, run adb shell pm verify-app-links --re-verify <your.package.name> to trigger another attempt and check again. This is far more reliable than tapping links and guessing.

Does DeepTap use the Play Install Referrer for deferred deep linking?

No. DeepTap matches a click to an install using a server-side fingerprint of the client IP and the device platform, within a short window after the click, and returns the saved destination through one HTTPS call on first launch. Install Referrer is a real Android API and a reasonable thing to add on top for campaign parameters, but it is not what DeepTap uses and we are not going to describe a mechanism we have not built.

Do I have to configure DNS?

Not on the default setup. You pick a subdomain such as myapp.deeptap.io and we host and serve assetlinks.json at the required path with the right headers and certificate. Custom branded domains on your own hostname are available on the Pro plan. What you still configure yourself is the intent filter in your manifest, which every option requires because that is how Android works.

How long does verification take?

It happens asynchronously around install and update time rather than instantly, and it needs network access to reach your domain. Give it a few minutes on a fresh install before concluding something is broken, and use the adb commands above rather than waiting and guessing.