All articles
OTT Engineering12 min read

Universal Links and Android App Links connecting a verified web domain to a streaming app's video player

Universal Links and App Links for Streaming Apps: Setup Guide

A viewer taps "Watch episode 4" in your newsletter. On one phone, your app opens on episode 4. On another, the episode page loads in the browser, asks them to sign in, and offers a web player they never use. Same link, same app installed, two very different outcomes.

The difference is almost always Universal Links on iOS or Android App Links on Android, and one of a handful of small configuration mistakes. This guide covers how both systems work, the exact files and settings a streaming app needs, how to design URLs for a catalog of shows and episodes, and how to debug links that open the browser instead of the app.

How Universal Links and App Links work

Universal Links (iOS) and Android App Links are standard HTTPS links that the operating system opens in your app instead of the browser, because your domain has published a file proving it trusts that app. If the app is not installed, the same URL simply opens your website.

That last part is why streaming apps should prefer them over custom URL schemes such as myott://show/123. A custom scheme fails outright when the app is missing, and any app can claim the same scheme. An HTTPS link always has a working fallback, and only an app your domain has verified can claim it.

iOS Universal LinksAndroid App Links
Domain fileapple-app-site-association (AASA)assetlinks.json
Locationhttps://yourdomain/.well-known/https://yourdomain/.well-known/
App-side settingAssociated Domains entitlement: applinks:yourdomainIntent filter with android:autoVerify="true"
App identity in fileTeam ID + bundle IDPackage name + SHA-256 signing certificate fingerprint
Path controlcomponents rules in the AASA fileIntent filter path, pathPrefix, or pathPattern
When verifiedFetched via Apple's CDN on install or updateChecked at install by the domain verification agent

Neither system handles users who do not have the app yet. For those, a deferred deep link carries the destination through the app store, which we cover in how deferred deep linking cuts OTT CAC.

Design URLs your catalog can live with

Get the URL structure right before writing any configuration. Every share button, email, push notification, and ad will use it for years.

Good streaming URLs are:

  • Built on stable content IDs. /watch/s/48213/e/7 survives retitling and localisation. /watch/the-night-shift-season-2-episode-7 breaks when marketing renames the show.
  • Readable where it helps. You can add a slug after the ID, such as /show/48213/the-night-shift, as long as routing ignores the slug.
  • The same on web and app. If /show/48213 is a valid web page, the app link and the web fallback stay in sync automatically.
  • Explicit about what stays on the web. Account settings, billing, help pages, and password resets often work better in the browser.

A typical path plan for an OTT app looks like this:

PathOpensNotes
/show/{id}Series pageMain target for ads and shares
/watch/{id}Player for a film or episodeCheck entitlement before starting playback
/live/{eventId}Live event or channelFall back to the event page if it has ended
/collection/{id}Curated row or genreUseful for seasonal campaigns
/account/*, /billing/*WebsiteExcluded from app links

iOS setup: the AASA file and Associated Domains

The AASA file tells iOS which app may open which paths on your domain. For a streaming app it might look like this:

{
  "applinks": {
    "details": [
      {
        "appIDs": ["ABCDE12345.com.example.ott"],
        "components": [
          { "/": "/account/*", "exclude": true },
          { "/": "/billing/*", "exclude": true },
          { "/": "/show/*" },
          { "/": "/watch/*" },
          { "/": "/live/*" },
          { "/": "/collection/*" }
        ]
      }
    ]
  }
}

Rules are evaluated in order and the first match wins, so exclusions must come before broader rules. A catch-all "/": "*" placed at the top silently shadows every exclusion below it.

Host the file at https://yourdomain/.well-known/apple-app-site-association with no file extension, served over HTTPS as JSON with a direct 200 response and no redirects. Then add applinks:yourdomain to the app's Associated Domains entitlement.

iOS does not download the file live from your server each time. Apple's CDN fetches and caches it, and the device picks it up when the app is installed or updated, so a fix can take time to reach users. Deeplinkly's free AASA and assetlinks.json generator builds both files from your app details, and its Universal Link tester shows which of your URLs will open the app and which rule decided it, without building the app.

Android setup: assetlinks.json and verified intent filters

On Android, the domain publishes assetlinks.json and the app declares which hosts and paths it handles.

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.ott",
      "sha256_cert_fingerprints": ["AA:BB:CC:...:FF"]
    }
  }
]
<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="watch.example.com" />
  <data android:pathPrefix="/show/" />
  <data android:pathPrefix="/watch/" />
  <data android:pathPrefix="/live/" />
</intent-filter>

Three details cause most Android failures:

  • The wrong fingerprint. With Play App Signing, Google re-signs your app, so the fingerprint must come from the Play Console, not your local keystore. Add your debug fingerprint as well if you test debug builds.
  • A custom scheme in the same filter. autoVerify applies to the whole intent filter. Mixing myott:// with HTTPS hosts makes verification fail for every host in it. Keep them in separate filters.
  • Every host listed separately. example.com and www.example.com are verified independently, and each must serve its own assetlinks.json.

Since Android 12, unverified HTTPS links go straight to the browser instead of showing an app chooser, so broken verification that used to look like "one extra tap" now looks like "the link does not work". Run adb shell pm get-app-links com.example.ott on an Android 12 or later device to see the verification state for each host. Deeplinkly's glossary entry on Android App Link verification explains each state it reports.

Verified domain files linking iOS and Android devices to a streaming title page

Route the link to the right screen in the app

Verification only gets the URL into your app. What happens next decides whether the viewer watches.

Handle the incoming link in one place on each platform, parse the path into a route such as Show(48213) or Watch(91022), and run the same checks your normal navigation runs:

  1. Profile selection. If the household has profiles, ask once, then continue to the destination. Kids profiles should not open a link to a mature title.
  2. Sign-in and entitlement. Store the destination, send unsigned users through login or sign-up, then resume. If the title needs a plan the user lacks, show a paywall that names the show.
  3. Availability. Titles expire and rights vary by country. Open the series page or a related title rather than an error.
  4. Playback start. For /watch links, open the player screen ready to play, not auto-playing with sound, unless the user tapped an explicit play action.
  5. Analytics. Record which link opened the app so share and campaign links show up in reporting. Our video analytics software guide covers the event model.

Do not forget the warm-start case. A link tapped while the app is already running arrives through a different callback from a cold start on both platforms, and it is the path most often left untested.

Living-room devices are a separate job. Roku, Fire TV, Apple TV, Android TV, Tizen, and webOS each have their own launch-parameter or deep-link mechanism, covered in our smart TV app development guide.

Debug links that open the browser instead of the app

When a link opens the website, work from the domain inward.

SymptomLikely causeFix
Opens browser on every iOS deviceAASA unreachable, redirected, or invalid JSONServe a direct 200 over HTTPS at /.well-known/
iOS opens the app for some paths onlyA rule order problem or missing componentPut exclusions first; test paths against the file
iOS fix not taking effectApple's CDN still has the old fileWait for the cache to refresh; reinstall to re-fetch
Android opens Chrome after a releaseFingerprint does not match the Play-signed buildUse the Play Console fingerprint
Android legacy_failure stateDomain returned the wrong file, type, or a redirectServe application/json with a direct 200
Works when tapped, not when typedExpected: typing a URL into the browser never opens the appTest by tapping links in Notes, Messages, or email
Opens the website inside a social appSome in-app browsers do not hand links to the OSShow an explicit "Open in app" button on the web page

Deeplinkly's Deep Link Debugger checks your live domain files, which covers the most common half of these problems in one step.

Build it yourself or use a link service

You can host the AASA and assetlinks files on your own domain and handle routing in the app. That covers installed users completely. What it does not give you is deferred deep linking for new installs, click and install attribution, or short branded campaign links with analytics.

Deeplinkly adds those on top of the same standards. It supports branded custom link domains, provides SDKs for iOS, Android, Flutter, React Native, and web, and its iOS SDK docs and Android SDK docs cover the app-side setup step by step. When Apexnova builds a streaming app, link routing is part of the navigation architecture from the first sprint rather than something bolted on before launch.

Frequently asked questions

Why does my Universal Link open Safari instead of my streaming app?

The most common causes are an AASA file that redirects, returns an error, or is not valid JSON; a missing applinks: entry in Associated Domains; or an exclusion rule that matches before the path you expect. Typing a URL directly into Safari also never opens the app.

Do Universal Links work if the app is not installed?

The link still works, but it opens your website instead of the app. To send new users through the App Store and then into the right show on first launch, you need deferred deep linking on top of Universal Links.

How long does an AASA file change take to reach users?

iOS gets the file through Apple's CDN and refreshes it on install or app update, so a change is not instant. Deleting and reinstalling the app on a test device forces a fresh fetch.

Which SHA-256 fingerprint goes in assetlinks.json when using Play App Signing?

Use the app signing key certificate fingerprint shown in the Play Console, because Google signs the version users install. Add your upload or debug fingerprint too if you need links to work on those builds.

Should a streaming app use custom URL schemes at all?

Keep a custom scheme only for cases that need it, such as returning from a payment or sign-in flow. For anything shared or promoted, use HTTPS Universal Links and App Links, which have a web fallback and cannot be claimed by another app.

Ship links that open the show every time

If your shares, emails, and push notifications are opening the browser, the fix is usually a day of careful work: clean URL design, correct domain files, separate intent filters, and routing that respects profiles, sign-in, and rights. If you also run paid campaigns to new users, plan for deferred deep linking and attribution at the same time so you only set up your link domain once.

Generate your AASA and assetlinks.json files free with Deeplinkly, or talk to Apexnova if you want deep linking built into your streaming app's navigation from day one.