Skip to main content

Routing & fallbacks

Every click on a short link ends at one of three places: your app, an app store, or a web page you choose. This page explains how that decision is made per click, what each link field and app-config entry contributes, and what happens when a link can't resolve at all. The wire contract of the endpoints involved is in the Resolution API.

The ladder​

At the top of the decision is the visitor's device:

  • Desktop visitors go straight to the web fallback. There is no app to open.
  • iOS and Android visitors get the full ladder. The page tries the app first; if the app doesn't answer, the store; if there's no store destination, the web fallback.

The fallback chain at the bottom of the ladder is: the link's web fallback URL first, then the App Store URL from your iOS app config, then the Google Play URL from your Android app config. The web fallback is a per-link field inherited from the template; leave it empty and the store destinations carry the traffic. See Link anatomy for the field.

How the app leg works​

Two mechanisms can open the app:

  • OS-verified links (Universal Links on iOS, App Links on Android): the OS checks your link domain's association files, decides the app owns the URL, and opens it directly. The browser never loads a page. The SDK fetches the link's path and parameters and navigates.
  • In-page routing: when verification isn't in place, or the visitor's browser can't hand off, the tap lands on the redirect page. The page then tries the app by platform convention: on Android, an intent URL with the store URL as its built-in fallback (Chrome) or a URI scheme with a timeout (Samsung Internet, Firefox); on iOS, where page-driven scheme attempts produce browser errors, the page hands off to Safari for OS routing or points at the store.

The server picks which page variant to send by parsing the browser's User-Agent, so a Samsung visitor and an iOS Safari visitor get differently wired HTML for the same link. In-app browsers on iOS (Instagram, Facebook, Slack and friends) block Universal Link routing, so the page offers an "Open in Safari" button instead, which pops the visitor into Safari where Universal Links can work.

If the app doesn't answer within its short timeout, the page continues down the ladder on its own: store buttons appear, and the store URL loads after a pause unless the visitor has already left.

What the app configuration contributes​

Each leg of the ladder exists only if the corresponding configuration exists for your organization:

ConfigurationEnablesMissing means
iOS app config: bundle ID + store URLApp Store destination and hand-offNo App Store button; iOS visitors end at the web fallback
Android app config: package name + store URLPlay destination and the intent fallbackNo Play button; Android visitors end at the web fallback
iOS teamId + bundle IDUniversal Links on your domainsiOS taps land on the redirect page instead of the OS
Android fingerprints + package nameApp Links on your domainsAndroid taps land on the redirect page instead of the OS
URI scheme (Android config)Scheme fallback attempts on Samsung Internet and FirefoxThe page relies on intent URLs and verified links only

Register all of it once in the onboarding wizard: Set up your organization.

Custom domains and association files​

Association files are served per domain, which is why custom domains matter for routing:

  • On the OptoLink default domain, one set of files covers every organization at once: any org's app can receive its links immediately, with no setup.
  • On a verified custom domain, the files describe only your organization's apps, so Universal Links and App Links verify against your brand.

A custom domain in its 30-day removal window keeps serving its association files, so links on it keep routing until the domain is actually deleted. The domain lifecycle: Domains: default vs custom.

A link can stop resolving at any point, and the endpoint answers with a JSON error rather than a page:

StateStatusEffect
Unknown short code or org key404The link never existed or was deleted
Link deactivated410Stops resolving immediately, resumes if reactivated
Link past its expiry date410Permanent: expiry doesn't undo itself
Organization suspended410Every link in the org stops resolving

Deactivation and expiry are different states with the same consequence: no redirect, no app open, no click recorded as a redirect. The error body shape and status semantics: Rate limits & errors.

When the visitor is a known link-preview crawler (WhatsApp, X/Twitter, Facebook, Telegram, Slack, Discord and the rest), there is no routing at all: the server returns a static page carrying the link's social metadata and nothing else. Crawlers don't execute JavaScript, so the page exists purely to make shares look right. The metadata falls back link → template → organization defaults, and crawlers cache it for 60 seconds.