Links to the site open the browser instead of the app
“Tapping a link to our domain in Mail, Messages or Slack opens Safari or Chrome even though the app is installed. It worked on my test phone, or it worked last release.”
THE PRINCIPLE
The OS, not your app, decides who handles an https link. It does that from a cached, signed-off association between domain and binary; when that association is missing or stale, the browser wins without an error.
FIRST MOVES
- Fetch both files the way the OS does: https://<host>/.well-known/apple-app-site-association and /.well-known/assetlinks.json must return 200 directly, with no redirects (including apex to www or a login/WAF challenge), valid TLS, and for Android content-type application/json.
- Check identities: AASA appIDs must be TeamID.bundleID for the build you ship; assetlinks.json needs the SHA-256 of the Play App Signing certificate (copy it from Play Console > App signing), not your local upload key. Debug, staging and production builds each need an entry.
- On Android 12+, run adb shell pm verify-app-links --re-verify <pkg>, then pm get-app-links <pkg>. Reset first with pm set-app-links --package <pkg> 0 all. Hosts left at none, legacy_failure or an error code of 1024+ open in the browser; each host in an autoVerify intent filter needs its own file.
- On iOS, remember the CDN: Apple fetches AASA within 24 hours and devices recheck about weekly, so a fixed file can look broken for days. Use the associated domain ?mode=developer during development to bypass it.
- Rule out expected behavior: a universal link tapped on a page of the same domain in Safari stays in Safari, and links behind redirect-style click trackers may never reach the OS as your domain.
TOOLS: THEN → NOW
Custom URL schemes (myapp://) for every linkVerified https links, with schemes kept for app-to-app callbacks
Firebase Dynamic Links for deferred deep linksUniversal links and App Links plus your own install attribution; Dynamic Links shut down in August 2025
Trial and error on devicepm get-app-links, Play Console deep links tool, Android Studio App Links Assistant, Expo Router's linking config
PATTERN SNAPSHOT
# Check what the OS will fetch: status, redirects, content type.
for path in apple-app-site-association assetlinks.json; do
curl -sS -o /dev/null --max-redirs 0 \
-w "$path %{http_code} %{content_type} redirect=%{redirect_url}\n" \
"https://example.com/.well-known/$path"
done
# Android 12+: reset, force verification, then read the per-host result.
adb shell pm set-app-links --package com.example.app 0 all
adb shell pm verify-app-links --re-verify com.example.app
adb shell pm get-app-links com.example.appCLOSE THE AI. EXPLAIN THIS.
Why does adding a new subdomain to the Android intent filter, without publishing assetlinks.json there, leave that host opening in the browser?RELATED SYMPTOMS
SOURCES
- Apple: Supporting associated domains (opens in new tab)Checked
- Apple: Allowing apps and websites to link to your content (opens in new tab)Checked
- Android Developers: Verify App Links (opens in new tab)Checked
- Android Developers: Configure website associations (assetlinks.json) (opens in new tab)Checked
Guide reviewed