How to Add a Referral Program to an AI-Built App

A referral code and a counter is a demo. The program is the five cases where money leaves your account and should not.

Steve Jefferson
Steve Jefferson
Developer Advocate
16 August 20261 min read

How to add a referral program to an AI-built app: generate a unique code per user, store an attribution row when someone new arrives with that code, and pay the reward only after the referred account does something you cannot fake for free. The generator will build the first two parts in about a minute. It will almost never build the third, and the third is the entire program.

Ask an AI app builder for "a referral system" and you reliably get a referral_code column, a share link, and a counter. That is a demo. What separates a demo from a program is the set of cases where money leaves your account and should not.

The mistake in most generated implementations is storing referral state as counters on the user row. Counters cannot be audited, cannot be reversed cleanly, and cannot answer "why does this person have 40 credits."

Model it as events instead. Three tables carry a real program.

sql
create table referral_codes (
  code        text primary key,
  owner_id    uuid not null references users(id),
  created_at  timestamptz default now(),
  disabled_at timestamptz
);

create table referrals (
  id            uuid primary key default gen_random_uuid(),
  code          text not null references referral_codes(code),
  referrer_id   uuid not null references users(id),
  referred_id   uuid not null references users(id),
  status        text not null default 'pending',
  qualified_at  timestamptz,
  created_at    timestamptz default now(),
  unique (referred_id)
);

create table referral_rewards (
  id          uuid primary key default gen_random_uuid(),
  referral_id uuid not null references referrals(id),
  user_id     uuid not null references users(id),
  amount      numeric not null,
  kind        text not null,
  reversed_at timestamptz,
  created_at  timestamptz default now()
);

Two details in there do most of the work. unique (referred_id) means an account can be referred exactly once, forever, which kills the most common abuse in a single line. And reversed_at on rewards rather than deletion means a clawback leaves a trail you can explain to the person it happened to.

The five cases generators skip

Work through these before you ship. Each one is cheap to handle now and expensive to handle after somebody has found it.

Self-referral. The same human creating a second account to refer themselves. You cannot detect this perfectly, so aim for friction rather than certainty: block when the referrer and referred share an email domain alias (the user+tag@ form), a payment fingerprint, or a device identifier. Log the block rather than silently dropping it, because you will want to see how often it fires.

Reward on registration. If the reward pays out when an account is created, you have built a machine that converts free accounts into money. Pay on a qualifying event instead: a first payment, a completed onboarding milestone that costs real effort, or fourteen days of retention. Pick the cheapest signal that a bot will not produce.

Double credit on retry. Network timeouts mean your reward endpoint gets called twice with the same intent. Without an idempotency key you pay twice. Stripe's idempotent requests are the standard model to copy: the caller supplies a key, you store it, and repeats return the original result rather than doing the work again.

Refund and chargeback clawback. You paid a reward because the referred user paid you, then they refunded. If your reward is credits, set reversed_at and deduct. If it is cash, decide your policy in advance and write it in the terms, because deciding it while arguing with someone is worse.

Attribution windows and last touch. Someone clicks a referral link, leaves, and returns three weeks later through a search. Do you pay? Pick a window, usually 7 to 30 days, and store the click time so the rule is mechanical. A partial index keeps the lookup cheap:

sql
create index referrals_pending_idx
  on referrals (referrer_id)
  where status = 'pending';

Partial indexes are one of those Postgres features generated code never reaches for and that pay off immediately on a table where you only ever query one status.

Prompting for the real version

The prompt that produces a usable first pass names the cases explicitly. Vague requests produce the demo.

Add a referral program with these rules, and implement all of them:

- one referral code per user, stored in its own table so codes can be
  disabled without touching the user row
- a referral row is created when a new account arrives with a code,
  status pending
- an account can be referred at most once, enforced by a database
  constraint, not application code
- reward fires only when the referred account makes its first payment,
  not at registration
- the reward endpoint takes an idempotency key and returns the original
  result on repeat calls
- rewards are reversible: a refund on the referred account sets
  reversed_at and adjusts the balance
- block referrals where referrer and referred share a payment
  fingerprint, and log every block

Write the migration first, then the service functions, then tests that
cover: double referral attempt, duplicate reward call, refund clawback.

Asking for the tests by name is what stops the model shipping the happy path and calling it finished.

Choosing the reward

The economics matter more than the implementation. A referral is worth paying for only if the referred user's lifetime value exceeds the two rewards you hand out plus your normal acquisition cost. For most small products, two-sided credit works better than cash: it costs you marginal delivery rather than currency, and it keeps both people inside the product.

A rough sanity check before you launch: if every current user referred exactly one person tomorrow, what would the reward bill be, and could you pay it? If that number frightens you, the reward is too generous or it should be capped per referrer.

Cap it. A per-user monthly cap on rewards is one line of logic and it converts your worst case from unbounded to known.

The share surface decides whether any of this matters

A correct referral system with a badly placed share prompt produces zero referrals, and you will conclude that referrals do not work for your product. They do. The prompt was in the wrong place.

The rule is to ask at the moment of demonstrated satisfaction, not at the moment of registration. Concretely, that means after a user completes the thing your product is for: the first successful export, the tenth booking, a five star rating, the end of a job that went well. A share prompt on the dashboard is furniture and gets ignored within a week.

Three practical details make a measurable difference.

Pre-fill the message, and keep it short. A share sheet that opens with empty text converts far worse than one that opens with a sentence the user can send unchanged. Write it in the user's voice rather than your marketing voice: "I've been using this for my invoices, you get £10 off if you use my link" beats anything with the word "platform" in it.

Show the reward status, not just the code. People check. A small list of pending and qualified referrals, with the reason each is pending, prevents the support ticket where somebody insists they referred four friends. Your referrals table already has exactly this data, which is one more argument for the ledger design.

Make the link work before the account exists. The code has to survive the landing page, any interstitial, and the registration flow. Store it in a cookie or local storage on first touch and read it at account creation. This is the single most common place referral tracking silently breaks, and it fails in a way nobody notices, since the account is created successfully and only the attribution disappears.

Test that specific path deliberately. Open your referral link in a fresh private window, wander around the marketing site for a while, then create an account, and check that a referrals row exists. Do it on mobile too, where storage behaviour differs.

Ship it behind a flag

Referral programs attract attention from exactly the people who enjoy finding edge cases. Launch to a subset, watch the block log and the reward ledger for a week, then widen. The ledger design above means "show me every reward paid this week and why" is one query, which is the difference between noticing an exploit in three days and noticing it in the monthly numbers.

FAQ

Should the referrer or the new user get the reward?

Both, usually. One-sided rewards to the referrer make the share feel self-interested, and the referred person has no reason to use the link over a plain visit. Two-sided rewards convert better and read better.

When should the reward actually pay out?

At the first event that costs the referred user real effort or real money. First payment is the cleanest. Registration is the worst choice available, because it is free to produce at volume.

How do I stop people gaming the program?

You do not stop it entirely. You make it unprofitable: pay on qualifying events, enforce one referral per account in the database, cap rewards per referrer per month, and keep rewards reversible.

Do I need a separate tool for this?

Not for a first version. The three tables above plus an idempotent reward endpoint cover what a hosted referral tool does for small products, without another vendor in your payment path.

Related builds: the full guide to building an app with AI, adding payments to an AI-built app, adding user accounts, and getting your first 100 users.

How did this land?

About the author

Steve Jefferson
Steve Jefferson

Developer Advocate

Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.

Share

Get the next post in your inbox

One email a month. Product updates, engineering posts, and the best of Built with Swarmz.

I agree to receive emails about AI building tips and Swarmz product news. Unsubscribe any time.