← Abdelrahman Saed · All case studies
Deep links and install attribution for Flutter and React Native apps, built solo on Cloudflare.
Creator & sole engineer · 2026 — Present · iOS · Android
DecTog is deep linking, deferred deep linking and install attribution for Flutter and React Native apps. A tap on a DecTog link opens the installed app on the right screen. If the app isn't installed, the tap goes through the right app store, and the new user still lands on that screen at first launch. The install is credited to the link that brought it, and the app's later events and revenue are credited to that link too.
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 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 DecTog. Each learner shares one personal link. The server credits the invited friend's install to that learner, and a signed webhook grants the inviter's free premium days. The customer's side of that story is on dectog.com. This study is the builder's side: the architecture, the attribution logic, and the decisions I made and 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 DecTog 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:
DecTog has one engineer, so I own all of it. Every commit in the server repository and both SDK repositories is mine.
flutter_dectog (Dart) and react-native-dectog (TypeScript, Expo-compatible), plus the shared Kotlin and Swift code underneath both.The whole service is one Cloudflare Worker and one D1 (SQLite) database. The Worker handles link taps on <username>.dectog.com, 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.
1. Exact signals win. These are the store install referrer carrying the click ID, Meta's on-device referrer, the link copied to the iOS clipboard, and the Apple Search Ads token, which the server checks with Apple. If one of these arrives late, for example because the store answered after first launch, the SDK sends it as a claim and the server upgrades the stored result. 2. Otherwise, a scored match. Only when no exact signal names a click does the server compare the install with the app's recent mobile web clicks. It uses a salted IP hash, device model, OS version, language and screen. It looks back 24 hours, or 2 hours when the device model is missing or the phone is on mobile data. 3. Credit is separate from routing. A high-confidence match gets the credit. A medium-confidence match only tells the app which screen to open. If the best matches point to different links, nobody gets credit.
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 dectog.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 dectog.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 D1 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.
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