Deep links fail silently. When a Universal Link does not open the app, the user just sees a web page. No error message, no crash log, no alert. You only find out when someone reports it, or when your analytics show a 40% fallback rate. Testing catches these issues before users encounter them.
This guide covers tools and techniques for testing deep links. For debugging specific issues, see debugging deep links: a step-by-step guide. For implementation, see how to implement deep links from scratch.
Verification File Validators
Apple App Site Association Validator
Apple provides a validator for AASA files:
App Search API Validation Tool
Enter your domain and the tool checks:
- AASA file is accessible at
/.well-known/apple-app-site-association - Returns HTTP 200 (not a redirect)
- Valid JSON format
- Correct
content-type: application/jsonheader applinkssection exists with valid app IDs
You can also check manually:
# Fetch and validate AASA
curl -s https://yourdomain.com/.well-known/apple-app-site-association | python3 -m json.tool
# Check headers
curl -sI https://yourdomain.com/.well-known/apple-app-site-association
# Must return: content-type: application/json (NOT text/html or text/plain)
# Must return: HTTP 200 (NOT 301/302 redirect)
Google Digital Asset Links Validator
Google provides a validator for assetlinks.json:
Statement List Generator and Tester
Enter your domain and Android package name. The tool verifies:
assetlinks.jsonis accessible at/.well-known/assetlinks.json- File contains a valid statement linking the domain to the app
- Package name matches
- SHA-256 certificate fingerprint matches
# Manual check
curl -s https://yourdomain.com/.well-known/assetlinks.json | python3 -m json.tool
# Verify specific link
curl -s "https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://yourdomain.com&relation=delegate_permission/common.handle_all_urls"
Command-Line Testing
iOS: xcrun and simctl
Test Universal Links on the iOS Simulator:
# Open a Universal Link in the Simulator
xcrun simctl openurl booted "https://yourdomain.com/products/123"
# If the app opens to the correct screen, the Universal Link is working
# If Safari opens instead, the AASA configuration is wrong
For real devices:
# Check AASA from the device's perspective (iOS 14+)
# On-device: Settings > Developer > Universal Links > Diagnostics
# Enter your domain to see if the AASA was downloaded successfully
Android: adb
Test App Links on a connected Android device or emulator:
# Open an App Link
adb shell am start -a android.intent.action.VIEW \
-d "https://yourdomain.com/products/123" \
-c android.intent.category.BROWSABLE
# Check if the app handles the URL
adb shell pm get-app-links com.your.package
# Verify App Links status
adb shell am compat enable 175408749 com.your.package # Enable strict verification
adb shell pm verify-app-links --re-verify com.your.package
adb shell pm get-app-links com.your.package
# Look for: Status: verified
curl: Quick Verification Check
# Check AASA file
curl -sL -o /dev/null -w "%{http_code} %{content_type} %{url_effective}\n" \
https://yourdomain.com/.well-known/apple-app-site-association
# Check assetlinks.json
curl -sL -o /dev/null -w "%{http_code} %{content_type} %{url_effective}\n" \
https://yourdomain.com/.well-known/assetlinks.json
# Check for redirects (which break verification)
curl -sI https://yourdomain.com/.well-known/apple-app-site-association | grep -i location
# If this returns a Location header, there is a redirect, which is a problem
Browser-Based Testing
Notes App (iOS)
The simplest Universal Links test on iOS:
- Open the Notes app.
- Type or paste your deep link URL (e.g.,
https://yourdomain.com/products/123). - Tap the link.
- If the app opens, Universal Links are working.
- If Safari opens, they are not.
Important: do not paste the link into Safari's address bar. Typing a URL directly into the address bar does not trigger Universal Links; it is treated as a navigation, not a link click.
Messages App (iOS)
Send yourself an iMessage with the deep link URL. Tapping it in Messages is one of the most reliable ways to trigger a Universal Link because Messages does not use an in-app browser.
Chrome (Android)
Type the deep link URL in Chrome's address bar on Android. If App Links are verified, the app should open automatically without a disambiguation dialog. If you see "Open with: Chrome or YourApp," the App Links verification may have failed.
Automated Testing
CI/CD Integration
Add deep link verification to your CI/CD pipeline:
# GitHub Actions example
name: Deep Link Verification
on:
push:
branches: [main]
schedule:
- cron: '0 */6 * * *' # Every 6 hours
jobs:
verify:
runs-on: ubuntu-latest
steps:
- name: Check AASA
run: |
STATUS=$(curl -sL -o /dev/null -w "%{http_code}" \
https://yourdomain.com/.well-known/apple-app-site-association)
if [ "$STATUS" != "200" ]; then
echo "AASA file returned $STATUS"
exit 1
fi
CONTENT_TYPE=$(curl -sI \
https://yourdomain.com/.well-known/apple-app-site-association \
| grep -i content-type | tr -d '\r')
if [[ ! "$CONTENT_TYPE" == *"application/json"* ]]; then
echo "Wrong content-type: $CONTENT_TYPE"
exit 1
fi
- name: Check assetlinks.json
run: |
STATUS=$(curl -sL -o /dev/null -w "%{http_code}" \
https://yourdomain.com/.well-known/assetlinks.json)
if [ "$STATUS" != "200" ]; then
echo "assetlinks.json returned $STATUS"
exit 1
fi
- name: Validate AASA JSON
run: |
curl -s https://yourdomain.com/.well-known/apple-app-site-association \
| python3 -c "import json,sys; json.load(sys.stdin)" \
|| (echo "Invalid JSON in AASA" && exit 1)
Route Testing
Test that each deep link route resolves to the correct screen:
interface DeepLinkTestCase {
url: string;
expectedScreen: string;
expectedParams: Record<string, string>;
}
const testCases: DeepLinkTestCase[] = [
{
url: 'https://yourdomain.com/products/abc123',
expectedScreen: 'ProductDetail',
expectedParams: { productId: 'abc123' }
},
{
url: 'https://yourdomain.com/offers/summer-sale',
expectedScreen: 'OfferDetail',
expectedParams: { offerId: 'summer-sale' }
},
{
url: 'https://yourdomain.com/referral/user456',
expectedScreen: 'Referral',
expectedParams: { referrerId: 'user456' }
}
];
function testRouteResolution(testCases: DeepLinkTestCase[]) {
for (const tc of testCases) {
const result = resolveDeepLink(tc.url);
assert(result.screen === tc.expectedScreen,
`${tc.url}: expected ${tc.expectedScreen}, got ${result.screen}`);
assert(deepEqual(result.params, tc.expectedParams),
`${tc.url}: params mismatch`);
}
}
Testing Checklist
Before Launch
| Test | iOS | Android | Desktop |
|---|---|---|---|
| AASA/assetlinks.json returns 200 | Yes | Yes | N/A |
| No redirects on verification files | Yes | Yes | N/A |
| App opens from Notes/Messages tap | Yes | N/A | N/A |
| App opens from Chrome address bar | N/A | Yes | N/A |
| Correct screen for each route | Yes | Yes | N/A |
| Fallback page works | Yes | Yes | Yes |
| In-app browser handling | Yes | Yes | N/A |
| Deferred deep link (fresh install) | Yes | Yes | N/A |
| Desktop shows web content + QR code | N/A | N/A | Yes |
After Each Deployment
| Test | Method |
|---|---|
| Verification files still accessible | curl check in CI |
| App opens from a link | Manual test or automated device test |
| No new routes missing from AASA | Compare routes in code vs AASA paths |
| Analytics tracking fires on click | Check analytics dashboard |
Monthly
| Test | Method |
|---|---|
| Test on top 5 Android manufacturers | Real devices (Samsung, Xiaomi, Huawei, Oppo, Pixel) |
| Test from major email clients | Gmail, Outlook, Yahoo Mail |
| Test from social apps | Instagram, Facebook, Twitter/X |
| Test deferred deep link end-to-end | Fresh install from each source |
Tolinku for Deep Link Testing
Tolinku hosts your AASA and assetlinks.json files automatically, eliminating the most common configuration errors. Use the troubleshooting guide to diagnose issues.
For debugging specific problems, see debugging deep links: a step-by-step guide. For the complete deep linking guide, see the complete guide to deep linking in 2026.
Get deep linking tips in your inbox
One email per week. No spam.