Skip to main content
Documentation
Concepts

Deep Linking

How deep linking works across iOS and Android, from Universal Links and App Links to routing users into the right screen in your app.

Deep links are URLs that take users directly to specific content inside a mobile app instead of a web page. WarpLink creates short links that resolve at the edge and route users to the right destination based on their platform. See Deep linking for the commercial overview of what this unlocks for your app.

How It Works

When a user taps a WarpLink URL (e.g., https://aplnk.to/abc123):

  1. Edge resolution: WarpLink resolves the link at the edge (sub-10ms redirects, how we measure this)
  2. User-Agent parsing: WarpLink parses the browser's User-Agent to detect the platform (iOS, Android, desktop)
  3. Bot detection: If the UA matches a known bot (social media crawlers, search engines), WarpLink returns an HTML page with OG/Twitter Card tags for rich previews
  4. Platform redirect: Real users get a 302 redirect to the appropriate destination:
    • iOS with app installed: Universal Link opens the app directly
    • Android with app installed: App Link opens the app directly
    • iOS without app: Redirect to the App Store (or custom fallback URL)
    • Android without app: Redirect to the Play Store (or custom fallback URL)
    • Desktop: Redirect to the web destination URL

Universal Links are Apple's mechanism for deep linking. When configured correctly, tapping a link opens your app without going through Safari.

Requirements:

  • An Apple App Site Association (AASA) file hosted at /.well-known/apple-app-site-association on the link domain
  • The Associated Domains entitlement in your app (applinks:yourapp.aplnk.to)
  • The app must be installed on the device

WarpLink generates and hosts the AASA file automatically when you register your iOS app. It's regenerated whenever you update your app configuration. If a link still opens Safari, Apple App Site Association Not Working walks through the fault-isolation checks, from curl to swcutil.

App Links are Android's equivalent of Universal Links. They require a Digital Asset Links file for domain verification.

Requirements:

  • An assetlinks.json file hosted at /.well-known/assetlinks.json on the link domain
  • An intent filter with android:autoVerify="true" in your AndroidManifest.xml
  • The app must be installed and verified

WarpLink generates and hosts assetlinks.json automatically when you register your Android app with your package name and SHA256 signing key fingerprint. If verification fails, Android App Links autoVerify Failed lists the adb checks and the Play App Signing fingerprint trap.

Every deep link decision, the platform detected, the app installed state assumed, and the fallback chosen, reads from a link configuration that has to be available on every request, at edge latency, everywhere. Postgres is the source of truth: a link write always lands there first. From there it's pushed to a globally distributed key-value cache, and it's that cache the redirect path actually reads from on the hot path, since a database round trip on every tap would break sub-10ms redirects.

Every link is written to the database first and then to the edge cache. If a request misses the cache, the redirect path reads the link from the database.

Fallback URLs are part of that same cached configuration, not a separate lookup: the app's store URL is resolved and baked into the link's cached record at write time, so the redirect path never has to make an extra decision about which app a link belongs to while a real visitor is waiting on it.

Fallback Cascade (2 Tiers)

When a user doesn't have the app installed, WarpLink determines the fallback destination using a cascade:

PrioritySourceExample
1Link-level fallback URLios_fallback_url on the specific link
2App-level store URLApp Store URL from app registration

Fallback URLs are baked into the link configuration at write time, so redirects require no extra lookups.

Every WarpLink URL uses a short slug format:

  • App links: https://yourapp.aplnk.to/{slug} (an app that existed before subdomains can also use https://aplnk.to/{slug})
  • Existing links with no app: https://aplnk.to/{slug}
  • Custom domains: https://yourdomain.com/{slug}

Slugs are auto-generated (6-character alphanumeric via NanoID) or custom-chosen. Custom slugs must be 2-100 characters, lowercase alphanumeric with hyphens.

An app that existed before subdomains keeps aplnk.to for good. Links already on it stay there and keep opening your app, and aplnk.to stays one of the app's allowed domains. Each such app also has its own address to set up when it is ready. Existing apps keep using the domain of their most recent link by default (aplnk.to when that domain cannot serve new links), and any team member can change it per app in the app's Settings, at any time. Choosing a default never changes a link you already have. After a rename, the old subdomain keeps working. If we later move its links to the new one, each old URL redirects with a permanent 301. See Your app's own link address.

Every SDK recognizes aplnk.to out of the box. Declare your app's subdomain and any custom domain in the SDK so links on them resolve from the very first launch: see Use it in your app.

Click Analytics

Every tap that reaches a link is recorded, but only unique taps count toward your plan's monthly click allowance. A tap is unique the first time it arrives from a given visitor on that link within a rolling 24 hour window, so repeat taps from the same visitor in that window are recorded in your analytics but are not billed. The link's analytics page splits recorded taps under Where clicks came from:

  • Browser redirects: the tap reached the link in a browser and WarpLink issued the 302
  • In-app opens: a verified Universal Link or App Link opened your app directly and the SDK resolved the link, so the tap never touched a browser

In-app opens appear in analytics alongside browser redirects. The get_link_analytics MCP tool returns the same split under sources.

Nothing is recorded for a request WarpLink refuses, such as an expired link or a password-protected one, and nothing is recorded for a known bot. A social crawler fetching a preview never inflates a link's count.

Troubleshooting

The most common deep linking problem is a link that opens the browser instead of the app, even though the app is installed. This has three usual causes:

  • Associated Domains or App ID not enabled. On iOS, the Associated Domains capability (applinks:yourapp.aplnk.to) must be added in Xcode and the same capability must be enabled on the app's App ID at developer.apple.com. On Android, the intent filter needs android:autoVerify="true". Missing either one means the platform never attempts verification, so it falls back to a browser.
  • AASA or assetlinks not verified. Even with the capability enabled, the platform still has to fetch and validate the association file (apple-app-site-association on iOS, assetlinks.json on Android) against the app's signing details. A stale device cache, a mismatched Team ID or SHA-256 fingerprint, or a file that fails validation all produce the same silent fallback to the browser.
  • Testing on a simulator. On the iOS Simulator, xcrun simctl openurl booted opens a Universal Link only when the app's Associated Domains entitlement lists the host and the domain serves a valid apple-app-site-association file that matches the path, and because tapping a link inside the Simulator is unreliable, confirm on a physical device. Android emulators can verify App Links, but results are less reliable than a physical device, so confirm a failure on a real device before troubleshooting further.

For a full diagnostic walkthrough, see Universal Links Not Opening: Every Cause and How to Fix Each One and Apple App Site Association Not Working: The iOS Universal Links Debug Checklist for iOS, and Android App Links autoVerify Failed for Android. To check both association files for a domain in one step, use our deep link checker.