Android App Links let your app open directly when a user taps a URL that belongs to your domain. No disambiguation dialog. No browser redirect. The user taps a link, and your app handles it immediately.
This is different from regular deep links or custom URI schemes. Android App Links are verified: Google confirms that you own the domain and that the domain explicitly trusts your app. Once verification passes, Android routes matching URLs straight to your app without asking the user to choose between your app and a browser.
This guide covers every step of the implementation process, from hosting the Digital Asset Links file to handling incoming links in Kotlin, testing with adb, and debugging common verification failures. For iOS, see the equivalent guide: Universal Links: everything you need to know.
How Android App Links Differ from Regular Deep Links
Android supports three types of links that can open apps:
Custom URI schemes (e.g.,
myapp://product/123). These are not web URLs. They work only if the app is installed, and any app can claim the same scheme. There is no ownership verification.Standard deep links (regular
http/httpsintent filters withoutautoVerify). These trigger the disambiguation dialog, where Android asks the user which app or browser should handle the URL.Android App Links (
http/httpsintent filters withautoVerify="true"). These skip the disambiguation dialog entirely because Android has verified that the domain owner authorized the app.The key difference is trust. With App Links, you prove domain ownership through a Digital Asset Links file hosted on your server. Android downloads this file during app installation (or update) and checks it against your app's signing certificate. If everything matches, your app becomes the default handler for those URLs.
For a broader overview of how App Links fit into the deep linking landscape, see the Tolinku App Links concepts documentation.
The Verification Flow
Here is what happens under the hood when a user installs your app:
- Android reads your app's manifest and finds intent filters with
android:autoVerify="true". - For each declared domain, Android sends a request to
https://{domain}/.well-known/assetlinks.json. - Android parses the JSON response and checks whether your app's package name and SHA-256 certificate fingerprint are listed.
- If the file is valid and the fingerprint matches, Android marks your app as the verified handler for that domain.
- From that point forward, any URL matching your intent filter pattern opens your app directly.
If verification fails for any domain in your intent filter, Android falls back to the disambiguation dialog for URLs on that domain. Verification must succeed for every domain you declare; a single failure does not block other domains from verifying.
On Android 12 (API 31) and later, the verification process changed. For details on Android 12+ changes, see Android 12 App Links changes. The system now uses a domain verification API that re-verifies links periodically rather than only at install time. This means fixing a broken
assetlinks.jsonfile can resolve verification without requiring users to reinstall your app.The Digital Asset Links File
The Digital Asset Links file is the cornerstone of App Links verification. It is a JSON file hosted at a specific path on your domain.
File Location
The file must be accessible at:
https://yourdomain.com/.well-known/assetlinks.jsonFor a step-by-step setup walkthrough, see the Digital Asset Links setup guide. No redirects. No HTTP (it must be HTTPS). The response must have a
Content-Typeofapplication/json. The URL must return a200status code.File Format
[ { "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.example.myapp", "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:90" ] } } ]The file is a JSON array. Each object declares a relationship between your domain and an Android app. You can list multiple apps (for example, a production build and a debug build) by adding more objects to the array.
Getting Your SHA-256 Fingerprint
For your debug keystore:
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass androidFor your release keystore:
keytool -list -v -keystore /path/to/your-release-key.keystore -alias your-aliasIf you use Google Play App Signing, you need the fingerprint from the Play Console, not from your local upload key. Go to Play Console > Your App > Setup > App signing and copy the SHA-256 fingerprint from the "App signing key certificate" section.
This is a common mistake. If you use Play App Signing (which is now required for new apps), your local keystore fingerprint will not match what Android uses for verification on production devices. You need both fingerprints in your
assetlinks.json: the Play signing key for production and your upload key for local testing.[ { "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.example.myapp", "sha256_cert_fingerprints": [ "AA:BB:CC:...:PLAY_SIGNING_KEY_FINGERPRINT", "DD:EE:FF:...:UPLOAD_KEY_FINGERPRINT" ] } } ]Setting Up Intent Filters
Your app declares which URLs it handles through intent filters in
AndroidManifest.xml.Basic Intent Filter
<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" /> </intent-filter> </activity>The critical attribute is
android:autoVerify="true". Without it, Android treats the intent filter as a regular deep link and shows the disambiguation dialog.Path Matching Options
Android supports three path matching attributes:
Attribute Example Matches android:path/product/featuredExact path only android:pathPrefix/product/product,/product/123,/product/abc/detailsandroid:pathPattern/product/.*/detailsPaths matching the pattern (uses .*for wildcards)For most deep linking setups,
pathPrefixprovides the right balance between specificity and flexibility.Multiple Domains
If your app handles links from multiple domains, add separate
<data>elements or separate intent filters:<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" /> <data android:host="yourdomain.com" /> <data android:host="www.yourdomain.com" /> <data android:pathPrefix="/product" /> </intent-filter>Remember: each domain listed here must have its own valid
assetlinks.jsonfile. Ifwww.yourdomain.comdoes not serve the file correctly, verification fails for that domain (thoughyourdomain.comcan still verify independently on Android 12+).For more details on configuring your Android app with Tolinku, see the Android configuration guide.
Handling Deep Links in Your Activity
Once Android opens your activity through an App Link, you need to read the incoming URL and navigate the user to the right screen.
Basic Kotlin Handling
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) handleDeepLink(intent) } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) handleDeepLink(intent) } private fun handleDeepLink(intent: Intent) { if (intent.action != Intent.ACTION_VIEW) return val uri = intent.data ?: return val path = uri.path ?: return when { path.startsWith("/product/") -> { val productId = uri.lastPathSegment navigateToProduct(productId) } path.startsWith("/profile/") -> { val username = uri.lastPathSegment navigateToProfile(username) } path.startsWith("/promo") -> { val code = uri.getQueryParameter("code") navigateToPromo(code) } else -> { // Fallback: open the home screen navigateToHome() } } } }Two things to notice. First, you must handle the intent in both
onCreate(cold start) andonNewIntent(app already running). If you skiponNewIntent, links tapped while the app is in the background will be silently ignored. Second, always include a fallback for unrecognized paths so users do not land on a blank screen.Jetpack Compose with Navigation
If you are using Jetpack Compose with the Navigation component, you can declare deep links directly in your navigation graph:
@Composable fun AppNavigation() { val navController = rememberNavController() NavHost( navController = navController, startDestination = "home" ) { composable("home") { HomeScreen() } composable( route = "product/{productId}", arguments = listOf(navArgument("productId") { type = NavType.StringType }), deepLinks = listOf( navDeepLink { uriPattern = "https://yourdomain.com/product/{productId}" } ) ) { backStackEntry -> val productId = backStackEntry.arguments?.getString("productId") ProductScreen(productId = productId) } composable( route = "profile/{username}", deepLinks = listOf( navDeepLink { uriPattern = "https://yourdomain.com/profile/{username}" } ) ) { backStackEntry -> val username = backStackEntry.arguments?.getString("username") ProfileScreen(username = username) } } }The Navigation component handles the URL parsing and argument extraction for you. When the activity receives an App Link intent, the NavHost matches the URI pattern and navigates to the correct composable. You still need the intent filter in your manifest; the
navDeepLinkdeclaration only handles routing within Compose, not the system-level registration.Extracting Query Parameters
Deep links often carry data as query parameters. Here is how to extract them:
private fun handleDeepLink(intent: Intent) { val uri = intent.data ?: return // For a URL like: https://yourdomain.com/referral?code=ABC123&source=email val referralCode = uri.getQueryParameter("code") // "ABC123" val source = uri.getQueryParameter("source") // "email" val allParams = uri.queryParameterNames // Set<String> // Use the extracted data if (referralCode != null) { applyReferralCode(referralCode, source) } }App Links Verification Details
Understanding how Android verifies App Links helps you avoid common pitfalls.
Verification Timing
On Android 11 and earlier, verification happens once: when the app is installed or updated. If your
assetlinks.jsonfile is not ready at install time, verification fails and stays failed until the user reinstalls the app.On Android 12+, the system uses a background domain verification process that can re-check domains. You can also manually trigger re-verification:
# Reset verification state (Android 12+) adb shell pm set-app-links --package com.example.myapp 0 all # Trigger re-verification adb shell pm verify-app-links --re-verify com.example.myappSubdomain Handling
Each subdomain is treated as a separate domain. If your manifest declares
app.yourdomain.com, Android will checkhttps://app.yourdomain.com/.well-known/assetlinks.json. It will not fall back toyourdomain.com. You must host the file on the exact subdomain listed in your intent filter.Server Requirements
Your server must meet these requirements for the
assetlinks.jsonfile:- Served over HTTPS with a valid certificate
- No redirects (a
301or302to another URL will cause verification to fail) Content-Type: application/json- HTTP status
200 - File size under 1 MB (keep it small; the file is fetched on every verification)
- Accessible from Google's servers (not blocked by robots.txt, geo-restrictions, or firewalls)
Common Problems and Solutions
Verification Fails Silently
Verification failures do not produce user-visible errors. The app simply does not open for App Links. Check the device logs:
adb shell dumpsys package d | grep -i "yourdomain.com"On Android 12+, use the more specific command:
adb shell pm get-app-links com.example.myappThis outputs the verification status for each domain. Look for
verified(success) ornone(failure).Wrong Certificate Fingerprint
The most common cause of verification failure. If you use Google Play App Signing, the fingerprint in your
assetlinks.jsonmust match the Play signing certificate, not your upload key. Grab the correct fingerprint from the Play Console.Redirects on assetlinks.json
Some hosting providers redirect
httptohttpsor addwww.prefixes. Theassetlinks.jsonURL itself must not redirect. Ifhttps://yourdomain.com/.well-known/assetlinks.jsonredirects tohttps://www.yourdomain.com/.well-known/assetlinks.json, verification fails.CDN or Caching Issues
If you use a CDN, make sure it does not transform the JSON response, add HTML wrappers, or serve a cached error page. Test the raw response:
curl -v https://yourdomain.com/.well-known/assetlinks.jsonConfirm the response is valid JSON with the correct
Content-Typeheader.Multiple Apps Sharing a Domain
You can list multiple apps in the same
assetlinks.jsonfile. Each app gets its own object in the array:[ { "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.example.myapp", "sha256_cert_fingerprints": ["AA:BB:CC:..."] } }, { "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.example.myapp.debug", "sha256_cert_fingerprints": ["DD:EE:FF:..."] } } ]For more help with App Links issues, the Tolinku Android troubleshooting guide covers additional edge cases.
Testing App Links
Thorough testing prevents surprises in production. For a complete testing reference, see testing Android App Links.
Using adb
The fastest way to test App Links locally is with
adb:# Test a specific URL adb shell am start -a android.intent.action.VIEW \ -c android.intent.category.BROWSABLE \ -d "https://yourdomain.com/product/123" \ com.example.myappIf the app opens and navigates to the product screen, your intent filter and deep link handling are working.
Checking Verification Status
# Android 12+ (API 31+) adb shell pm get-app-links com.example.myapp # Older Android versions adb shell dumpsys package com.example.myapp | grep -A 5 "App Links"Google Digital Asset Links Validator
Google provides an online validator for your
assetlinks.jsonfile. Enter your domain and package name, and the tool checks whether your file is correctly formatted and accessible.Real Device Testing
Emulator testing has limitations. Some verification behaviors differ on real devices, especially around Play Store signing. Always test on a physical device with a release build before shipping.
Steps for a production-like test:
- Build a signed release APK or App Bundle.
- Install it on a device:
adb install app-release.apk. - Wait 20-30 seconds for verification to complete.
- Open a browser and tap a link to your domain.
- Confirm the app opens without a disambiguation dialog.
App Links with Tolinku
Hosting and maintaining the
assetlinks.jsonfile is straightforward for a single app, but it gets complicated when you manage multiple domains, staging environments, or frequently rotating signing keys.Tolinku handles the
assetlinks.jsonhosting for you. When you configure an Appspace with your Android app details (package name and SHA-256 fingerprints), Tolinku automatically generates and serves the Digital Asset Links file on your configured domain. Every route you create in Tolinku becomes a working App Link without manual server configuration.This is especially useful when you need to support multiple environments. Your debug build, staging build, and production build can all have their fingerprints registered in one place. Tolinku's platform serves the combined
assetlinks.jsonwith all the correct entries.For the full setup walkthrough, see the App Links developer guide in the Tolinku documentation.
Best Practices
Always include both
wwwand non-wwwdomains. Users might encounter either version of your URL. Declare both in your manifest and hostassetlinks.jsonon both.Use
pathPrefixoverpathPatternwhen possible. Path patterns use a custom regex syntax that behaves differently from standard regex. Prefix matching is simpler and less error-prone.Keep your
assetlinks.jsonfile small. Only include the apps that actually need to handle links on that domain. Bloated files with dozens of entries can slow verification.Monitor verification status after releases. If your signing key changes or your CDN configuration shifts, App Links can break silently. Add a health check that periodically fetches your
assetlinks.jsonand validates the contents.Handle fallback gracefully. Even with App Links configured, some users will have link handling disabled in their device settings. Your website should still provide a good experience for those users, including a banner or redirect to the Play Store.
Support both
httpandhttpsin your intent filter. Whileassetlinks.jsonmust be served over HTTPS, some links in the wild may usehttp. Including both schemes in your manifest ensures your app catches those URLs too (Android will verify using HTTPS regardless).<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" /> <data android:scheme="http" /> <data android:host="yourdomain.com" /> <data android:pathPrefix="/product" /> </intent-filter>Test on multiple Android versions. Verification behavior changed significantly in Android 12. If your minimum SDK target is below API 31, test on both older and newer devices.
Version your deep link paths. If you restructure your URL patterns, old links will break. Consider keeping old path prefixes active or setting up server-side redirects for deprecated patterns.
Conclusion
Android App Links give you a direct path from a URL tap to your app's content. The setup has a few moving parts (manifest declarations, the Digital Asset Links file, certificate fingerprints, verification timing) but each piece is straightforward on its own.
The most important things to get right: host your
assetlinks.jsonwithout redirects, use the correct signing key fingerprint (especially with Play App Signing), and test on real devices before release. Once verification passes, your users get a fast, direct experience with no extra taps or choices.If you want to skip the manual
assetlinks.jsonmanagement and get working App Links alongside deep linking features like deferred deep links, analytics, and smart banners, Tolinku handles the server-side configuration for you.For further reading, check the official Android App Links documentation and the Digital Asset Links specification.
- Android reads your app's manifest and finds intent filters with
Get deep linking tips in your inbox
One email per week. No spam.