Glossary/Failure modes
Debugging deep links with adb
Definition
adb provides direct access to Android's domain verification state, intent resolution, and package manifest data, which together identify why a deep link opens the browser instead of the app.
Android is unusually good at this. Where iOS makes you infer the association state from behaviour, Android will print it: which domains your app claims, which verified, which failed, and what the system would do with any given URL. Nearly every Android deep link problem is diagnosable in three commands, provided you know which three.
The three that solve most problems
# 1. What does the system think about your domains?
adb shell pm get-app-links com.example.shop
# 2. Re-run verification after fixing assetlinks.json (no reinstall needed)
adb shell pm verify-app-links --re-verify com.example.shop
# 3. What would actually happen to a real tap? No package name, so the
# system resolves it the way a browser would.
adb shell am start -W -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "https://example.com/products/42"The first prints a per-domain state; anything other than verified is the answer to your question. The second is what makes a fix testable in seconds rather than requiring a reinstall. The third is the only launch command that reflects real behaviour — see how to test a deep link for why the version with a package name is misleading.
| State | Meaning | Next step |
|---|---|---|
verified | Working | If links still fail, the problem is your intent filter's paths or your routing |
none | Verification never ran | android:autoVerify="true" is missing from the intent filter |
1024 | Ran and failed | Fingerprint mismatch, or the file is unreachable |
legacy_failure | Failed unrecoverably | Malformed statement file; fix and --re-verify |
migrated | Inherited from a pre-Android 12 install | --re-verify to get a real verdict |
| Domain not listed | The system does not know you claim it | The host is not in any autoVerify intent filter |
Full command reference
# Per-domain verification state for one package
adb shell pm get-app-links com.example.shop
# Every app on the device that claims domains — useful when another app
# is competing for yours
adb shell pm get-app-links
# Force re-verification (Android 12+)
adb shell pm verify-app-links --re-verify com.example.shop
# Simulate the user approving your app for a domain, bypassing verification.
# Confirms your routing works while you fix the fingerprint.
adb shell pm set-app-links-user-selection --user 0 \
--package com.example.shop true example.com
# Reset link handling for the package back to defaults
adb shell pm set-app-links --package com.example.shop 0 all
# Pre-Android 12 devices used a different subsystem entirely
adb shell dumpsys package domain-preferred-apps# Honest test — no package, browsable category, like a real tap
adb shell am start -W -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "https://example.com/products/42"
# Routing test only — explicit package, always opens the app
adb shell am start -W -a android.intent.action.VIEW \
-d "https://example.com/products/42" com.example.shop
# Custom scheme
adb shell am start -W -a android.intent.action.VIEW -d "myapp://products/42"
# Which activity WOULD handle it, without launching anything
adb shell pm resolve-activity --brief \
-a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "https://example.com/products/42"
# Every activity that could handle it, in priority order
adb shell pm query-activities \
-a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "https://example.com/products/42"
# URLs with query strings and fragments need quoting, and & must be escaped
adb shell am start -W -a android.intent.action.VIEW \
-d "https://example.com/p/42?utm_source=test\&ref=abc#reviews"Unescaped `&` silently truncates your test URL
The URL passes through a shell on the device, so an unescaped ampersand ends the command there. Your test then runs against a URL missing everything after the first parameter — which is a different URL, and may match different rules than the one you meant to test.
# Full manifest data: intent filters, permissions, signatures
adb shell dumpsys package com.example.shop
# Just the intent filters — confirms what the INSTALLED build declares,
# which is not always what your source says
adb shell dumpsys package com.example.shop | grep -A 30 "android.intent.action.VIEW"
# Domain verification section
adb shell dumpsys package com.example.shop | grep -A 20 -i "domain"
# Installed version — confirm the device is running the build you think
adb shell dumpsys package com.example.shop | grep -E "versionName|versionCode"
# Which installer put it there. "com.android.vending" means Play, which
# means Play App Signing, which decides the fingerprint you need.
adb shell pm list packages -i com.example.shop
# Pull the APK off the device and read the actual signing certificate
adb shell pm path com.example.shop
adb pull /data/app/.../base.apk
keytool -printcert -jarfile base.apkThat last sequence settles the most common Android argument outright. It reads the fingerprint of the build actually installed on the device, which you can compare against what assetlinks.json declares — no guessing about which key Play used.
# Watch intent resolution and your app's handling in real time
adb logcat | grep -iE "intent|applink|deeplink|verif"
# Just your app's output
adb logcat --pid=$(adb shell pidof -s com.example.shop)
# Clear the buffer first so you only see this attempt
adb logcat -c && adb shell am start -W -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE -d "https://example.com/p/42"
# Send a synthetic install referrer to test YOUR PARSER.
# The real broadcast was removed from Play in 2020 — this exercises your
# handling logic, not whether the referrer actually arrives.
adb shell am broadcast \
-a com.android.vending.INSTALL_REFERRER \
-n com.example.shop/.InstallReferrerReceiver \
--es "referrer" "utm_source=test&utm_campaign=debug&dl=%2Fproducts%2F42"A working diagnostic order
Run these in sequence and stop at the first one that gives an unexpected answer. Each rules out a layer, so a failure at step 3 means steps 1 and 2 are not worth revisiting.
pm list packages -i— is the installed build the one you think, and who installed it? This determines which signing key is in play.dumpsys package | grep -A 30 VIEW— does the installed build declare the intent filter, withautoVerify,BROWSABLE, and the right host?pm get-app-links— did verification run, and did it pass?curl -sSI --max-redirs 0against/.well-known/assetlinks.json— is the file reachable, unredirected, andapplication/json?keytool -printcert -jarfileon the pulled APK — does the installed build's fingerprint appear in that file?pm resolve-activity— given all of the above, what does the system say it would do?am startwithout a package — does it actually do that?
`set-app-links-user-selection` unblocks the app team
Manually approving your app for a domain makes links route correctly while the real fingerprint problem is being sorted out on the web or release side. It is not a fix — it applies to one device — but it lets in-app routing be built and tested in parallel rather than blocked behind an infrastructure change.
deep link debugger
adb tells you what the device thinks; it cannot tell you whether your domain serves the right thing. The debugger covers the other half — fetching assetlinks.json as Android would, following redirects, checking the content type, and listing the fingerprints the file declares for comparison against your installed build.
Frequently asked questions
- How do I check if Android App Links are verified?
- Run adb shell pm get-app-links followed by your package name. It prints each domain your app claims and its verification state. Anything other than verified is a failure: none means verification never ran, usually because android:autoVerify is missing from the intent filter, and 1024 means it ran and failed, usually a SHA-256 fingerprint mismatch.
- How do I re-verify App Links without reinstalling the app?
- adb shell pm verify-app-links --re-verify followed by the package name, on Android 12 and later. Verification is asynchronous, so wait a few seconds and then re-read the state with pm get-app-links. This is what makes an assetlinks.json fix testable in seconds instead of requiring a full reinstall cycle.
- What is the difference between am start with and without a package name?
- With a package name it is an explicit intent that matches against the intent filter and never consults domain verification, so it opens the app whether or not the domain is verified. Without one, and with the BROWSABLE category included, the system resolves it the way a browser would, so it reflects what a real link tap does. Only the second is a valid test of your setup.
- How do I see which signing key an installed Android app uses?
- Get the APK path with adb shell pm path, pull it with adb pull, then run keytool -printcert -jarfile on the file. That gives the SHA-256 fingerprint of the certificate that actually signed the build on the device, which is the value that has to appear in assetlinks.json. It settles the Play App Signing question without guessing.
- Can I test App Links without fixing assetlinks.json first?
- Yes. adb shell pm set-app-links-user-selection --user 0 --package your.package true yourdomain.com manually approves your app for that domain on that device, bypassing verification. It lets the app team build and test in-app routing while the fingerprint or hosting problem is resolved separately. It affects only that device and is not a fix.
- Why does my adb test URL lose its query parameters?
- The URL passes through a shell on the device, so an unescaped ampersand terminates the command. Everything after the first parameter is dropped and you end up testing a different URL than you intended. Quote the whole URL and escape ampersands as \& so the full string reaches the intent.
Related terms
- Testing a deep link — Testing a deep link means verifying three separate things — that the operating system routes the URL to the app, that the app receives it, and that the app navigates to the right screen — each of which can pass while the others fail.
- Android App Links not working — Android 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.
- Validating assetlinks.json — Validating assetlinks.json means confirming four separate things: that Android can fetch the file, that it parses as a valid statement list, that it names the fingerprint of the shipped build, and that verification passed on a device.