← Back to Blog

Universal Links vs App Links: How They Work and Where They Stop

Universal Links (iOS) and App Links (Android) open your app from an HTTPS link when it's installed. How each works, how to debug them, and where they stop.

Quick answer: Universal Links (iOS) and App Links (Android) do the same job on different platforms: they let a normal HTTPS link open your app instead of the browser when the app is installed. If you ship on both platforms, you need both. Neither one helps a user who doesn't have the app installed yet. Covering that case is a separate job called deferred deep linking.

$ cat table-of-contents

  1. What's the difference between Universal Links and App Links?
  2. Do you need both?
  3. How do Universal Links work on iOS?
  4. How do App Links work on Android?
  5. What is actually different between them?
  6. Why does my link open the browser instead of the app?
  7. What happens when the app isn't installed?
  8. Is deep linking the same as attribution?
  9. What do you actually need?
  10. Where LinkTrace fits
  11. Frequently asked questions

What's the difference between Universal Links and App Links?

If you want a link to open a specific screen in your mobile app, both iOS and Android have a native solution. On iOS, that solution is Universal Links. On Android, it is App Links. They do the same job on different platforms, and neither one helps a user who doesn't have the app installed on their phone yet.

Say someone taps a link they saw in an ad or a friend shared with them, and it points to a specific page in your app, such as a product, an offer or a profile. Since the app isn't installed, the link opens your website. They install the app from the store, open it, and land on the home screen instead of that page, because the app has no way of knowing which link they tapped.

Covering that gap is a separate job called deferred deep linking. Most of this guide explains how Universal Links and App Links open an app that is already installed. The final sections cover what happens when the app isn't installed, and how deferred deep linking fills that gap.

Universal Links (iOS) App Links (Android)
Verification file apple-app-site-association assetlinks.json
Where it lives https://<domain>/.well-known/apple-app-site-association https://<domain>/.well-known/assetlinks.json
App-side declaration Associated Domains entitlement Intent filter with android:autoVerify="true"
App installed Opens the app and hands it the URL Opens the app and hands it the URL
App not installed Opens the URL in the browser Opens the URL in the browser
Which screen opens Your app's routing code decides Your app's routing code decides
Post-install routing (opens the linked page after a fresh install) Not by itself Not by itself (see the Play Install Referrer note below)

Two rows deserve a second look. First, with both Universal Links and App Links, the operating system only delivers the URL to your app; opening the right screen is routing code you write. Second, when the app is not installed, a Universal Link or App Link opens in the browser, not in the App Store or Play Store. Nothing sends the user to the store unless your web page does.

Do you need both Universal Links and App Links?

Yes, if your app is on both platforms. They are not alternatives. One HTTPS URL is claimed twice: once by your iOS app through a file Apple reads, and once by your Android app through a file Android reads.

            https://example.com/products/running-shoes
                              |
               +--------------+--------------+
               |                             |
              iOS                         Android
               |                             |
   apple-app-site-association          assetlinks.json
   + Associated Domains                + autoVerify intent filter
               |                             |
               +--------------+--------------+
                              |
                  App opens, receives the URL,
                  your code routes to the screen

Both files sit on the same domain. The link you share never changes.

iOS opens your app for a link when two things agree: your app says it handles a domain, and that domain says your app is allowed to. The steps, from Apple's associated domains documentation:

  1. Declare the domain in the app. Add the Associated Domains capability in Xcode with an entry such as applinks:example.com.
  2. Publish the association file. Host a file named apple-app-site-association, with no extension, at https://example.com/.well-known/apple-app-site-association.
  3. The system verifies. When a user installs the app, the system downloads the file and checks it against the entitlement.
  4. A matching link is tapped. The system launches your app and passes an NSUserActivity whose webpageURL holds the link.
  5. Your code routes. You parse the URL and navigate. Apple's guidance is to validate every parameter and discard malformed URLs, because this is an entry point into your app.

A minimal association file looks like this:

json: apple-app-site-association

{
  "applinks": {
    "details": [
      {
        "appIDs": ["ABCDE12345.com.example.app"],
        "components": [
          { "/": "/products/*" }
        ]
      }
    ]
  }
}

appIDs is your Team ID prefix plus bundle identifier. components lists the URL patterns the app should open.

Four details from Apple's docs cause most of the confusion:

  • No redirects. The file must be served over HTTPS with a valid certificate and no redirects. A 301 from example.com to www.example.com breaks it.
  • Every subdomain is separate. example.com, www.example.com and support.example.com each need their own entitlement entry and each must serve its own file.
  • Your server is not asked directly. Since iOS 14 and macOS 11, devices request the file from an Apple-managed CDN, not from your web server.
  • Changes are not instant. Apple's CDN requests your file within 24 hours, and devices check for updates roughly once a week after install.

A quick check that the file is reachable without a redirect:

bash

curl -sI https://example.com/.well-known/apple-app-site-association
# Expect: HTTP 200. A 301 or 302 means iOS will not accept it.

If your server isn't reachable from the public internet during development, Apple provides an alternate mode that bypasses the CDN, set with a ?mode= suffix on the entitlement entry.

Android uses the same two-sided agreement, with the app's half in the manifest and the domain's half in a Digital Asset Links file. The steps, from Android's App Links documentation:

  1. Declare the URLs in the manifest. Add an intent filter for your HTTPS host with android:autoVerify="true".
  2. Publish the association file. Host assetlinks.json at https://example.com/.well-known/assetlinks.json, naming your package and the SHA-256 fingerprint of its signing certificate.
  3. The system verifies. When the app is installed, Android fetches the file and checks that the fingerprint matches the app's signing certificate.
  4. A matching link is tapped. Android routes straight to your app, with no "open with" chooser.
  5. Your code routes. You read the URL from the incoming intent and navigate.

xml: AndroidManifest.xml

<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="example.com"
          android:pathPrefix="/products" />
</intent-filter>

json: 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:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
    }
  }
]

The details that matter, from Android's verification docs:

  • Availability. App Links work on Android 6 and later, on devices with Google services installed.
  • The fingerprint must match the installed build. Debug, release and store-signed builds can carry different certificates, so a file that verifies one may not verify another.
  • Older versions are all-or-nothing. On Android 11 and lower, your app becomes the default handler only if a matching file is found for every host in the manifest.
  • Verification is asynchronous. Android's docs say to wait at least 20 seconds after install before checking.
  • Changes are not instant. On Android 15 and higher the system re-verifies periodically, and a changed file can take up to seven days to reach all devices. On Android 14 and lower a change is typically picked up only when the app is installed or updated.

Check the result on a connected device:

bash

adb shell pm get-app-links com.example.app
# Look for:  example.com: verified

On Android 12 and higher you can reset and re-run verification without reinstalling:

bash

adb shell pm set-app-links --package com.example.app 0 all
adb shell pm verify-app-links --re-verify com.example.app

One newer capability: starting with Android 15, Dynamic App Links let you refine which paths, query parameters and exclusions open the app by editing assetlinks.json, with no app release. Server-side rules can narrow what the manifest declares but cannot widen it, so Android recommends a broad manifest filter and fine rules on the server.

What is actually different between them?

For the user, there is no difference: both Universal Links and App Links open the app from a normal HTTPS link. For the developer, they differ in three ways: who fetches the verification file, when it is re-checked, and where the path rules live.

Universal Links (iOS) App Links (Android)
Who fetches the file Apple's CDN, on the device's behalf (since iOS 14) Android, from your domain
When it is verified At install At install
When it is re-checked Devices check roughly weekly Android 15+: periodically. Android 14 and lower: on install or update
How long a change takes CDN within 24 hours, then the device's next check Up to seven days on Android 15+
Where path rules live In the server file (components) In the app manifest; Android 15+ can refine them in the server file
Check verification on a device Tap a real link from another app adb shell pm get-app-links <package>
Platform requirement Associated Domains capability on the App ID Android 6+, Google services on the device

The practical consequence: on iOS you can change which paths open the app without shipping a build, because Universal Link path rules have always lived in the apple-app-site-association file on your server. On Android, changing App Link paths without an app update only became possible with Android 15.

Why does my link open the browser instead of the app?

A Universal Link or App Link usually opens the browser instead of the app for one of two reasons: the domain is not verified for the app, or the link was tapped somewhere the operating system does not hand links to apps. Check verification before you suspect the link. Each cause below traces to Apple's associated domains documentation and Android's App Links verification documentation, both listed under Sources.

Cause Platform How to tell
The apple-app-site-association file is served through a redirect or a bad certificate iOS curl -sI on the file returns anything other than 200
The link's subdomain isn't the one you declared (www. vs bare domain) iOS Compare the link host to the entitlement entry, character for character
You changed the file and the device hasn't re-fetched it Both Wait for the next re-check (roughly a week on iOS, up to seven days on Android 15+), or reinstall the app
Verification hasn't finished or failed Android pm get-app-links shows a state other than verified
The certificate fingerprint in assetlinks.json doesn't match the installed build's certificate Android Compare the file against the build you are testing, not the one you shipped
One host in the manifest fails, so none are approved Android 11 and lower Remove hosts until verification passes
The device has no Google services Android App Links aren't available; links stay in the browser
The user turned off link handling for your app Android Settings, your app, "Open by default"

The second group of causes is about where the link was tapped. On iOS, pasting a link into Safari's address bar will not open the app: you have to tap the link from another app such as Notes or Messages, as our Enable Deep Linking guide notes. Links opened inside another app's built-in browser can behave differently from links tapped in Messages or Mail.

We tapped the same LinkTrace link from each of these places on 8 October 2026, with the Knudge.me store release installed on both phones: an iPhone 15 on iOS 26.6.2 and a OnePlus 12 on OxygenOS 14 (Android 14).

Where the link was tapped iOS result Android result
Messages / SMSOpened the appOpened the app
Notes (iOS) / Keep (Android)Opened the appOpened the app
GmailOpened the appOpened the app
WhatsAppOpened the appOpened the app
QR code via the cameraOpened the appOpened the app

What happens when the app isn't installed?

The link opens in the browser as an ordinary web page, on both platforms. Apple's docs say users without the app get the same information in a web browser; Android's say they go to your website. That is the whole native behaviour.

Two things follow that are easy to miss.

Nothing sends the user to the store. If you want them to install, your web page has to do it: a banner, a button, or a redirect that picks the App Store or Play Store by platform.

Nothing survives the install. Neither Universal Links nor App Links pass the tapped URL to an app that is installed afterwards. Take a link to example.com/products/running-shoes. A user without the app taps it, lands on your site, goes to the app store, installs, and opens the app. The app starts cold. The operating system hands it no URL, because no link was tapped to open it. The user sees the home screen, not the running shoes page.

App installed                       App not installed

Tap link                            Tap link
   |                                   |
OS verifies domain                  Browser opens your page
   |                                   |
App opens with the URL              Store (only if your page sends them)
   |                                   |
Your code routes                    Install, first open
   |                                   |
Running shoes                       Home screen. The link is gone.

This is the install gap, and closing it is what deferred deep linking means: the destination is stored on a server when the link is tapped, and the app asks for it on first open. The hard part is matching that first open to the right earlier tap. There are two ways to do it.

  • An exact hand-off, on Android only. Google Play's Install Referrer API lets an app retrieve the referrer URL of its install, along with click and install timestamps. It is exposed by the Play Store app, so it covers installs that come through Google Play.
  • A probabilistic match, on both platforms. iOS has no equivalent that passes an ordinary link's context to the app after install. The alternative is to compare signals recorded at the tap with signals from the first open, inside a time window. This works on both platforms, and it can miss. A careful implementation reports "no match" instead of guessing.

We cover the mechanics and failure cases in Deferred Deep Links vs Deep Links and the implementation guide.

Is deep linking the same as attribution?

No. Deep linking and attribution are two different jobs. A deep link answers "where should this user go?" Attribution answers "where did this user come from?"

A referral link such as example.com/invite/abc123 usually needs both. You want the new user to land on the invite screen, and you want to credit the person who sent it. The first is routing. The second is attribution. After an install, both depend on matching the new user back to the link they tapped, which is why one system often does both, but you can need either without the other.

What do you actually need?

If all you need is to open an already-installed app from an HTTPS link and land on a specific screen (the first three rows below), build it yourself with Universal Links, App Links and your own routing code. You do not need LinkTrace or any other third party.

What you want What does it Need a third party?
Open your installed iOS app from an HTTPS link Universal Links No
Open your installed Android app from an HTTPS link App Links No
Land on a specific screen Your app's routing code, on the URL the OS hands over No
Send people without the app to the right store A page or redirect that detects the platform No, but it is one more thing to host
Land on that screen after an install, Android only Play Install Referrer plus your own server No
Land on that screen after an install, iOS and Android Deferred deep linking with a matcher Build a matcher or use a service
Know which link or referrer produced an install Install attribution Build a matcher or use a service
Reconcile paid ad-network claims, SKAdNetwork or AdAttributionKit, fraud scoring A mobile measurement partner such as AppsFlyer, Adjust, Branch, Kochava, Singular or Airbridge Yes

The last row, reconciling paid ad-network claims and fraud scoring, is a different product category from deep linking. If you need to measure paid media at scale, a mobile measurement partner (MMP) is the right answer, not a deep linking tool. The LinkTrace guide Do You Need an MMP? covers how to tell whether you need one.

Where LinkTrace fits

LinkTrace is a deferred deep linking and install attribution service. It does three things: it sends users to the right app store, it lands them on the right screen after an install, and it tells you which link produced that install. It sits between building everything yourself and buying an MMP, and it does not replace an MMP. Integration takes two HTTPS calls, one to create a link and one to attribute the new user, and no SDK. The rest of this section states exactly what LinkTrace does and does not do today.

Creating a link. You attach the destination to the link as a small payload:

http: create a link

POST https://api.linktrace.in/api/v1/referral-links
Content-Type: application/json
x-api-key: YOUR_API_KEY

{
  "referrerIdentifier": "user_9821",
  "campaign": "summer_referral",
  "customPayload": {
    "screenName": "product_detail",
    "productId": "running-shoes"
  }
}

The response contains a short link of the form https://app.linktrace.in/r/56ff9241. customPayload holds up to three string key-value pairs.

When someone taps it. By default the link sends the user to the App Store or Play Store according to their platform. That is the store-routing row, handled without a page of your own.

If the app is already installed. Opening the app directly uses Universal Links and App Links on a branded subdomain such as yourbrand.linktrace.in, which is on the Scale plan ($29.99/month as of October 2026). LinkTrace hosts both verification files for that subdomain, scoped to its /r/ links; you add the Associated Domains entry and the intent filter described above. Today a verified link opens the app at its home screen. The app is handed the tapped URL, and the API's GET /api/v1/referral-links/metadata endpoint returns a link's customPayload when given its short code, so your backend can look up where a link was meant to go. Setup is in Enable Deep Linking.

If the app had to be installed first. After the new user signs up, your app calls the attribution endpoint once:

http: attribute the install

POST https://api.linktrace.in/api/v1/attributions
Content-Type: application/json
x-api-key: YOUR_API_KEY

{ "userId": "your_internal_user_id" }

json: response

{
  "success": true,
  "data": {
    "attributed": true,
    "referrerIdentifier": "user_9821",
    "campaign": "summer_referral",
    "customPayload": {
      "screenName": "product_detail",
      "productId": "running-shoes"
    }
  }
}

This match is probabilistic, within a 24-hour window from the tap, on both platforms. When there is no confident match the response returns attributed: false rather than guessing, and your app should fall through to its normal first-run flow. That is the common case for organic installs, so the default flow has to be a good one. The full walkthrough is in the Deferred Deep Linking guide.

What we measured. In our own test we removed the app and installed it fresh 10 times on each of the two phones above, opening the link from each source we tested. All 20 first opens returned attributed: true with the correct referrer.

Method: each first open came 15 to 30 minutes after the tap, on a mix of mobile data and one shared Wi-Fi network, between 5 and 8 October 2026. This is a small test on our own devices soon after the tap, which is the easiest case for a probabilistic match. It shows the flow works end to end; it is not an accuracy rate.

What it does not do.

  • It does not read the Play Install Referrer. If you are Android-only and install through Google Play, building on that API yourself gives you an exact hand-off that LinkTrace's probabilistic match does not.
  • It is not an MMP. Ad-network postbacks, ROAS modelling, SKAdNetwork and AdAttributionKit conversion values, and fraud scoring are MMP territory.
  • It does not remove the platform setup. To open an installed app from a link, you still add the Associated Domains entry and the intent filter yourself.

Frequently asked questions

Do Universal Links work if the app isn't installed?

No. Without the app, a Universal Link is an ordinary web link and opens in the browser. The same is true of App Links on Android. Getting the user to the store, and to the right screen after they install, are separate jobs.

Yes. The same HTTPS URL can be claimed by your iOS app through apple-app-site-association and by your Android app through assetlinks.json, both hosted on that URL's domain.

How long do changes to the verification files take to reach users?

On iOS, Apple's CDN requests your apple-app-site-association file within 24 hours and devices check for updates about once a week. On Android 15 and higher a change to assetlinks.json can take up to seven days; on Android 14 and lower it is typically picked up when the app is installed or updated.

Is deferred deep linking exact?

Only where the platform itself passes the link data through the install. On Android, Google Play's Install Referrer API does that for Play installs. On iOS there is no equivalent for ordinary links, so the match is probabilistic and can return no result.

Do I need an MMP for deep linking?

No. Universal Links and App Links are free platform features, and deferred deep linking can be built or bought on its own. An MMP is for measuring paid acquisition across ad networks, which is a different problem.

If you remember three things

  • Universal Links are iOS, App Links are Android, and a cross-platform app sets up both on one URL.
  • Both stop at the install. Without the app the link is a web page, and nothing carries it into a freshly installed app.
  • Deferred deep linking covers the install gap, exactly on Android through Google Play, probabilistically everywhere else.

Sources

Platform behaviour in this guide was checked against the following pages on 6 October 2026. Device tests were run between 5 and 8 October 2026.


LinkTrace is a deferred deep linking and install attribution service for startups and indie developers. Two HTTPS calls, flat pricing, no SDK. Start here.

Related guides: Deferred Deep Links vs Deep Links, Deferred Deep Links Are Harder Than They Look, and Do You Need an MMP?