DEV Community

TAMSIV
TAMSIV

Posted on

How I Built a Full Referral System in React Native in One Day (Deep Links, Stacking Rewards, Push Notifications)

Yesterday I spent 8 hours building something that has nothing to do with my product's core features. No new UI. No bug fix. No optimization. A referral system.

I'm building TAMSIV, a voice-powered task manager for Android. Solo dev, 660+ commits, 6 months in. I have 12 alpha testers on the Play Store and I need that number to grow — organically.

So I built the full referral loop. Here's how it works, what I learned, and the gotchas you'll hit if you try this in React Native.

The concept

Simple: every user gets a unique referral code. Share it. When someone signs up with it, both get 1 free month of Pro. And it stacks — 10 referrals = 10 months free, queued one after another.

The 3 capture sources (this is where it gets tricky)

The hardest part isn't generating codes. It's capturing them reliably across all the ways someone might arrive at your app.

1. Deep links via the website

When someone visits tamsiv.com/invite/CODE, Next.js redirects to the Play Store with the referral code embedded:

// next.config.ts
async redirects() {
  return [{
    source: '/invite/:code',
    destination: 'https://play.google.com/store/apps/details?id=com.tamsiv&referrer=:code',
    permanent: false,
  }]
}
Enter fullscreen mode Exit fullscreen mode

2. Android App Links (direct open)

If the app is already installed, tamsiv://invite/CODE opens it directly. This requires:

AndroidManifest.xml:

<intent-filter android:autoVerify="true">
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="https" android:host="tamsiv.com"
        android:pathPrefix="/invite" />
</intent-filter>
Enter fullscreen mode Exit fullscreen mode

Plus an assetlinks.json on your website for verification:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.tamsiv",
    "sha256_cert_fingerprints": ["YOUR_SHA256"]
  }
}]
Enter fullscreen mode Exit fullscreen mode

React Navigation linking config:

const linking = {
  prefixes: ['tamsiv://', 'https://tamsiv.com'],
  config: {
    screens: {
      Signup: 'invite/:code',
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

3. Play Store Install Referrer

This is the sneaky-good one. When someone clicks a Play Store link with &referrer=CODE, Android stores that referrer string. Even if they install the app 3 days later, you can still read it.

npm install react-native-play-install-referrer
Enter fullscreen mode Exit fullscreen mode
import { getInstallReferrer } from 'react-native-play-install-referrer';

const referrer = await getInstallReferrer();
// referrer.installReferrer contains your code
Enter fullscreen mode Exit fullscreen mode

This covers the "click link → browse Play Store → install later → open" flow that deep links can't handle.

Stacking rewards

Most referral systems give you one reward and that's it. I wanted rewards to accumulate. Each referral adds 30 days of Pro, queued after the previous one expires.

The logic: when a referral is validated, the backend checks if the user already has an active reward. If yes, the new reward's start date = previous reward's end date. If no, it starts now.

This required rethinking my subscription logic — RevenueCat handles paid subscriptions, but free months from referrals are managed separately in Supabase.

Push notifications in 6 languages

When someone uses your referral code, you get a push notification. In your language. Because TAMSIV supports 6 languages (FR, EN, DE, ES, IT, PT), the backend checks the referrer's language preference before sending via FCM:

// Backend endpoint: POST /api/push/referral-reward
const referrerProfile = await getUserProfile(referrerId);
const lang = referrerProfile.language || 'en';
const messages = {
  fr: `${referredName} a utilisé votre code ! 1 mois Pro offert 🎉`,
  en: `${referredName} used your code! 1 free month of Pro 🎉`,
  // ... 4 more languages
};
await sendPushNotification(referrerProfile.fcm_token, messages[lang]);
Enter fullscreen mode Exit fullscreen mode

The Invite tab

The frontend has a dedicated Invite section in the Social screen: your unique code, a native share button, referral stats (total invites, pending, rewarded), and reward history. All managed by a ReferralService singleton with caching.

By the numbers

  • 22 files changed across backend, frontend, website, and Android manifest
  • 1,324 lines added
  • 1 day of focused work
  • 6 languages supported
  • 3 capture sources for maximum coverage

What I learned

  1. Android App Links verification is fragile. The assetlinks.json must be served at /.well-known/assetlinks.json with the exact SHA256 of your signing key. Miss one character and it silently fails.

  2. Play Store Install Referrer is underrated. Most tutorials skip it, but it's the only way to capture referrals through delayed installs.

  3. Reward stacking adds real complexity. A simple "give 1 month free" is easy. Queuing multiple rewards requires tracking start dates, end dates, and the relationship between RevenueCat subscriptions and your own reward system.

  4. i18n multiplies everything. 1 push notification message × 6 languages × 2 recipients (referrer + referred) = 12 translation keys. For every user-facing string.

Will it work?

Honestly? No idea. The code is ready, the circuit is complete. Now it's up to the users.

But here's the thing — building the referral system forced me to think about growth for the first time. After 660 commits of pure product work, spending one day on distribution felt uncomfortable. But necessary.

If you're a solo dev reading this: don't wait as long as I did. Build the growth loop early.


TAMSIV is a voice-powered task manager for Android. Try it on the Play Store or visit tamsiv.com.

Follow the journey: GitHub commits in public | Discord

Top comments (0)