DL2App

Find out why a link does not open your app

Paste a domain to check its Apple and Android association files, or a full link to also see what Instagram, TikTok, LinkedIn, X and 16 other apps receive when someone taps it. Free, no sign-up, and it works for any domain.

Check for a specific app (optional)

Team ID, a dot, then the bundle ID.

The applicationId in your Gradle file.

10 checks an hour. A link is requested about 39 times, once per kind of app. Reports stay shareable for seven days.

What it checks

DNS and certificate
The host resolves, over which address families, and its HTTPS certificate is valid and not about to expire.
Apple file
apple-app-site-association is served with status 200 and no redirect, as JSON, under 128 KB, with valid app IDs. Your link's path is matched against its rules, including exclusions and wildcards.
Apple's cached copy
What Apple's servers currently hold for the host, compared with what yours sends. Phones read Apple's copy, so a stale one explains links that work for you and nobody else.
Android file
assetlinks.json is served with status 200 and no redirect, as JSON, with the right relation, package name and SHA-256 fingerprints in the right format.
Google's check
Whether Google's Digital Asset Links service, which Android uses to verify links, can see your app for this host.
The link itself
Fetched as 51 combinations of app and platform across 20 apps: every redirect, the final destination, whether in-app browsers get a button to tap, what preview crawlers receive, cache headers and response time.

The app-by-app part is a simulation: our server requests the link with each app's user agent. It shows what your link serves, not whether a phone then opens the app, and every report lists the rows that still need a tap on a real device, with a printable checklist.

Common failures and how to fix them

The file answers with a redirect

Apple and Android both refuse redirects for these files. The usual cause is the bare domain redirecting to www, or http to https happening on a different host. Serve the file on the exact host your links use.

The file is an HTML page

A single-page app's catch-all route or a bot-protection challenge is answering first. Serve /.well-known/* before the catch-all and exclude it from challenges.

Apple has an older copy

Apple fetches the file through its own servers and refreshes it on its own schedule, usually within a day. You cannot purge it. Test with applinks:yourhost?mode=developer on a development build.

Wrong Android fingerprint

With Play App Signing, Google re-signs your app. Use the app signing key from Play Console → Protected with Play → Play Store distribution → Go to Play app signing (the “App signing key” section), not your upload key. Fingerprints are uppercase, with colons.

Works in Safari, not in Instagram

In-app browsers load the link in their own webview, which is reported not to hand it to your app. Serve those browsers a small page with a button instead of a redirect.

Works when tapped, not when pasted

That is by design on both platforms: a URL typed or pasted into the address bar stays in the browser. Test by tapping the link in Notes or a message.

Setting it up

Xcode

Target → Signing & Capabilities → + Capability → Associated Domains. Add applinks:go.example.com. Then host this file at /.well-known/apple-app-site-association, with no file extension.

apple-app-site-association
{
  "applinks": {
    "details": [
      {
        "appIDs": ["ABCDE12345.com.example.app"],
        "components": [
          { "/": "/account/*", "exclude": true },
          { "/": "/*" }
        ]
      }
    ]
  }
}

Android Studio

Add an intent filter with android:autoVerify="true" for your host (Tools → App Links Assistant writes it), then host this file at /.well-known/assetlinks.json. Check with adb shell pm get-app-links your.package.

assetlinks.json
[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.app",
      "sha256_cert_fingerprints": ["14:6D:E9:83:…:44:E5"]
    }
  }
]

Expo

Both platforms are configured in app.json. Entitlements and intent filters are part of the native build, so make a new build after changing them; an over-the-air update is not enough.

app.json
{
  "expo": {
    "ios": { "associatedDomains": ["applinks:go.example.com"] },
    "android": {
      "intentFilters": [
        {
          "action": "VIEW",
          "autoVerify": true,
          "data": [{ "scheme": "https", "host": "go.example.com" }],
          "category": ["BROWSABLE", "DEFAULT"]
        }
      ]
    }
  }
}

DL2App hosts both files for you, serves the right answer to each in-app browser, and runs these checks on a schedule so you hear about a broken link before your users do.

Try DL2App