← Abdelrahman Saed · All case studies
Deep links and install attribution for Flutter and React Native apps, built by one engineer.
Creator & sole engineer · 2026 — Present · iOS · Android
AdTo is deep linking, deferred deep linking and install attribution for Flutter and React Native apps. A tap on an AdTo link opens the installed app on the screen the link points to. If the app isn't installed, the tap goes through the right app store, and AdTo tries to carry that screen through the install to the first launch. When the evidence is strong enough, the install is credited to the link that brought it, and so are the app's later events and revenue. When it isn't, the new user can still land on the right screen while no link gets the credit.
I built it alone in 2026, alongside my day job leading mobile at iStoria. The reason was close to home: iStoria was paying thousands of dollars a year for its referral tracking, and Firebase Dynamic Links, the free option many Flutter apps relied on, had been shut down.
iStoria's referral program now runs on AdTo. Each learner shares one personal link. When an invited friend signs up from an install credited to that link, a signed webhook tells iStoria to grant the inviter free premium days. The customer's side of that story is on adto.app. This study is the builder's side: the architecture, the attribution logic, and the alternatives I rejected along the way.
Deferred deep linking looks like a redirect, but the hard part is matching three separate moments. The tap happens in a browser. The install happens in an app store AdTo never sees. The first launch can come minutes or hours later, in an app that knows nothing about the tap. Connecting those three moments is the whole product.
Every way of connecting them has a cost. The exact signals exist on only some platforms. The fallback is a guess. And the guesses that work best are the ones Apple's rules and EU privacy law forbid.
It also had to run for very little money, and one person had to be able to operate it.
The rules every design had to follow:
AdTo has one engineer. Every commit in the server and both SDK repositories is mine:
flutter_adto and react-native-adto (Expo-compatible), on one shared Kotlin and Swift layer.AdTo is one TypeScript service (Hono) and one database. The service handles link taps on <username>.adto.app, the SDK API under /api/v1, the dashboard API, the AASA and assetlinks.json files, and an MCP server at /mcp. It also serves the dashboard, which is a static Next.js export. Two scheduled jobs do the rest: a daily cleanup, and an hourly job that retries webhooks and Apple Search Ads tokens and builds the daily totals.
If the app is installed, a tap opens it through App Links or Universal Links, the Android intent fallback, or a custom URL scheme. If it isn't, the tap goes to the right store: the App Store, Google Play, Huawei AppGallery, Galaxy Store, Xiaomi GetApps or vivo. On Android, the click ID travels inside the store's install referrer. On iOS, an optional tap-to-store page copies the link to the clipboard first.
On first launch the SDK registers the install once, with an ID that makes retries safe. It sends every exact signal it can collect.
The SDK side of iStoria's referral program is two calls:
// The inviter: one stable link per user, created with the app's SDK key.
final link = await adto.createReferralLink(customerId: currentUser.id);
share(link.url);
// The invited friend, at sign-up. The server records the referral from the
// link the install was credited to, so a modified app can't claim a reward.
await adto.identify(customerId: newUser.id);
The reward is never granted from the app. It comes from the signed referral.converted webhook, which the server sends only for a credited install.
The server decides attribution when the install arrives and stores the result. Per-link totals go into daily summary tables in the same database. There is no row per impression, no recomputed view, and no separate analytics database. Raw rows are deleted after the retention window (400 days by default), once the daily totals cover them.
Every API route is declared once, in one table. The OpenAPI spec and the route list are generated from it, and a test fails if a route and the table disagree. The server's API responses are saved as test files, and both SDK repositories keep synced copies. A separate file of test cases pins down how attribution behaves.
Each SDK keeps its logic in its own language on top of one shared native layer. That layer is Kotlin and Swift code for the store referrers, the clipboard and the Keychain ID, written once. The React Native repository copies it, and its CI fails on any difference. The React Native port took about 4,000 lines of TypeScript and a 400-line bridge, with no native code of its own.
Two places where I turned down the safer-looking option, and what that cost.
The alternative: credit only exact signals, and never pay a referrer for a scored match.
Why I rejected it: on iOS, an install with no copied link and no Apple Search Ads token has no exact signal at all. Exact-only credit would leave every one of those referrals unpaid, and the inviter would see a friend who joined and no reward.
The cost I accepted: a high-confidence match is still a guess, and two phones of the same model on one Wi-Fi network can fool it. Three limits keep it in check: a tie between links credits nobody, an app can stop installs that trip its fraud rules from earning credit, and an app that can't accept the risk can turn the scored match off and keep exact signals only.
The alternative: a managed message queue for failed webhook deliveries, which would retry within a minute.
Why I rejected it: it's one more service to set up, watch and pay for on every deployment, and one person runs AdTo.
The cost I accepted: a failed delivery waits in a database table for the hourly job, which sends up to 300 retries a run. A retry due five minutes later goes out at the next hourly run instead. The decision log says when to revisit this: when a receiver's backlog outgrows 300 an hour.
Available for hire. Abdelrahman Saed is a Lead Mobile Engineer (Flutter) — open to full-time, fractional, contract, or advisory work. Hire me →
Book a 20-minute call · Download the CV (PDF) · See how I work