One link that opens your app on the right screen, and sends people without the app to the store. Firebase Dynamic Links used to be the default way to get that, until Google shut it down in August 2025. tinbo does it on your own domain, without an SDK.
This post covers how it works, and what the hard part really is (it's not the part most vendors talk about).
What a deep link has to do
A deep link is a link that opens your app instead of a web page. Both platforms support this natively, as long as the app and the domain vouch for each other:
- iOS (universal links): the app lists the domain in its Associated Domains, and the domain serves an
apple-app-site-associationfile naming the app. - Android (App Links): the app declares the domain in an intent filter, and the domain serves an
assetlinks.jsonfile with the app's package name and signing key.
When both sides match, a tap on the link opens the app directly. That part is well understood. The awkward cases are everything around it:
- The app isn't installed. The person should get the App Store or Google Play, not a web page that pretends to be your app.
- The app gets installed from that link. On first launch it should still open the screen the link pointed to. This is the "deferred" deep link, and it's where the platforms stop helping.
- Computers. No app to open, so they get the website.
How it works on tinbo
go.acme.com/order/42/_/openEverything app-specific lives on the domain, once:
- In Domains → Deep links, you enter the Apple Team ID, bundle ID and App Store link, and the Android package name and signing-key fingerprints. tinbo serves both association files from that, under
/.well-known/on your domain. - The same settings become the domain's default device targeting: iPhones without the app go to your App Store page, Android phones to your Google Play page, built from the package name. Links don't carry any of this.
So a link is just a link. Its destination is the page it stands for, say https://acme.com/product/42. The app decides which screen that means, and computers get the page itself. When one link needs to differ, its Device targeting has an Override defaults button: point iPhones at a TestFlight invite and Android phones still go to Google Play.
On the app side there is no SDK. When a link opens the app, the app sends it to the domain and gets the destination back:
curl -X POST https://go.acme.com/_/open \
-H 'content-type: application/json' \
-d '{"link": "https://go.acme.com/order/42", "platform": "ios", "deviceType": "mobile"}'
{
"clickId": "…",
"link": {
"id": "…",
"domain": "go.acme.com",
"slug": "order",
"url": "https://acme.com/order/42"
}
}
That one call also counts the click (marked "opened in app", with the right device), and applies the link's geo targeting and A/B test, so the app gets the same destination a browser would. The deep links guide has the full code for SwiftUI, Kotlin and React Native.
One link can also cover many screens. Turn on prefix link, and go.acme.com/order answers go.acme.com/order/42 too, with /42 added to the destination. One link for every order, and the app routes by path.
Deferred deep links, honestly
This is the hard part, and the platforms treat it very differently.
Android does it properly. Google Play carries an "install referrer" from the store page to the app it installs. tinbo puts the short link into it, and on first launch the app reads it through the Install Referrer API and asks /_/open where to go. Nothing is guessed. The one condition: the store page has to be your own app's, which the domain default always is.
iPhone doesn't. The App Store passes nothing from a web page to the app it installs, and Apple offers no way to do it. Every deep-linking service works around this, and the workarounds have limits. tinbo uses two, in this order:
go.acme.com/order/42, no app yet/_/open{} to /_/open- The clipboard gives the exact link, but only if the person allows the paste. If they say no, that's fine: the app starts on its home screen, or tries the IP match.
- The IP match is a best guess, and it answers once per store visit. People sharing an IP (an office, a café, a carrier, a VPN) can get each other's link, and it misses anyone who took longer than an hour to install. If you'd rather not guess, don't make that call.
We think it's better to say this plainly than to promise "100% deferred deep linking". On iPhone nobody has that.
Coming from Firebase Dynamic Links?
Most of what a Firebase link carried as URL parameters now lives on the domain or in the link's own settings:
| Firebase Dynamic Links | tinbo |
|---|---|
link | The link's destination |
apn | Android package name, on the domain |
ibi, isi | Bundle ID and App Store link, on the domain |
afl, ifl | Override defaults: a device rule for that phone |
ofl | The destination (computers get it anyway), or a desktop rule |
st, sd, si | Custom social preview |
utm_* | UTM parameters in the destination |
| The short link's suffix | The slug |
If your old links used your own domain, it's still yours: point it at tinbo with one CNAME record, and links you recreate under their old slugs (in bulk, 100 per request) work again, including ones already printed or sent. page.link addresses belonged to Google, so those are gone for good.
What it costs
Deep links are on the Pro plan, $42 a month billed monthly, on a custom domain. That covers the association files, the store pages, deferred deep links on both platforms, and app opens counted with the rest of your clicks. No per-install or per-click fees.
The deep links guide walks through the setup step by step, from the domain settings to the code in your app.