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:
| Configuration | Enables | Missing means |
|---|---|---|
| iOS app config: bundle ID + store URL | App Store destination and hand-off | No App Store button; iOS visitors end at the web fallback |
| Android app config: package name + store URL | Play destination and the intent fallback | No Play button; Android visitors end at the web fallback |
iOS teamId + bundle ID | Universal Links on your domains | iOS taps land on the redirect page instead of the OS |
| Android fingerprints + package name | App Links on your domains | Android taps land on the redirect page instead of the OS |
| URI scheme (Android config) | Scheme fallback attempts on Samsung Internet and Firefox | The 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.
When a link doesn't resolve
A link can stop resolving at any point, and the endpoint answers with a JSON error rather than a page:
| State | Status | Effect |
|---|---|---|
| Unknown short code or org key | 404 | The link never existed or was deleted |
| Link deactivated | 410 | Stops resolving immediately, resumes if reactivated |
| Link past its expiry date | 410 | Permanent: expiry doesn't undo itself |
| Organization suspended | 410 | Every 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.
Link previews
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.