Requirements
- Users share a unique referral code or link to invite new users
- Referrer earns a reward when a referred user completes a qualifying action (signup, first purchase)
- Support one-sided and two-sided rewards (both referrer and referee get rewards)
- Prevent fraud: referral fraud, self-referrals, fake accounts
- 100M users, 1M referrals/month
Data Model
ReferralCode(code VARCHAR UNIQUE, referrer_id UUID, created_at,
uses_count INT, max_uses INT NULL)
Referral(referral_id UUID, referral_code VARCHAR, referrer_id UUID,
referee_id UUID NULL, -- NULL until referee signs up
status ENUM(PENDING,QUALIFIED,REWARDED,FRAUD,EXPIRED),
click_at TIMESTAMP, signup_at TIMESTAMP, qualified_at TIMESTAMP,
ip_address, device_fingerprint, utm_source)
ReferralReward(reward_id UUID, referral_id UUID, user_id UUID,
reward_type ENUM(CREDIT,DISCOUNT,CASH,POINTS),
amount DECIMAL, status ENUM(PENDING,ISSUED,EXPIRED),
issued_at, expires_at)
Referral Link Flow
- Referrer requests their referral code: GET /referral/code → generate or return existing code
- Referrer shares URL: https://app.example.com/signup?ref=ABC123
- Prospect clicks link → server stores referral_code in cookie/session (TTL=30 days)
- Prospect signs up → read referral_code from cookie → create Referral record (status=PENDING)
- Qualifying action completed (first purchase) → update status=QUALIFIED → issue rewards
Reward Issuance
Rewards should be issued asynchronously after the qualifying event:
def on_qualifying_action(user_id, order_id):
referral = db.query("SELECT * FROM Referral WHERE referee_id = :uid
AND status = 'PENDING'", uid=user_id).first()
if not referral: return
# Issue rewards in a transaction
BEGIN TRANSACTION
UPDATE Referral SET status='QUALIFIED', qualified_at=NOW() WHERE referral_id=:id
INSERT INTO ReferralReward (referral_id, user_id=referral.referrer_id, amount=10, status='PENDING')
INSERT INTO ReferralReward (referral_id, user_id=referral.referee_id, amount=5, status='PENDING')
COMMIT
# Apply rewards to accounts (async via job queue)
enqueue_reward_job(referral_id)
Fraud Prevention
Referral fraud is common and costly. Key fraud signals:
- Self-referral: same user creates a new account and refers themselves. Detection: same email domain, same IP address, same device fingerprint, same payment method as the referrer. Block if any match.
- Fake accounts: mass creation of fake accounts to farm referral rewards. Detection: velocity limit (max N signups from same IP per day), email domain blacklist (disposable email providers), phone number verification required for reward.
- Reward farming rings: coordinated groups of real users cross-referring. Detection: graph analysis — a cluster of users all referring each other is suspicious.
- Referral abuse: referring using shared/public links (posted on deal sites). Detection: velocity limit on a single referral code (max uses/day).
Fraud holds: instead of issuing rewards immediately, hold for 7-30 days (cooling period) to allow fraud detection. If account is flagged as fraud within the holding period, cancel the reward.
Attribution
Attribution determines which referral source gets credit when a user has multiple touch points. Last-touch attribution (default): the referral code from the most recent click gets credit. First-touch attribution: the first referral code the user clicked gets credit. The referral_code stored in the cookie is typically last-touch — overwrite with new code on each click. Store click history for analysis and dispute resolution.
Referral Code Generation
Codes must be short (for sharing), unique, and hard to guess. Options: (1) Random alphanumeric: 8 characters from [A-Z0-9] = 36^8 ≈ 2.8 trillion combinations. Collision probability with 100M users: < 0.001%. (2) User-ID-based encode: base62-encode the user_id. Shorter, deterministic, but exposes user ID count. (3) Vanity codes: let users set custom codes ("JOHN10"). Store in ReferralCode with UNIQUE constraint. Generate code on first request; cache in Redis key=referral_code:{user_id} permanently.
Key Design Decisions
- Cookie attribution with 30-day TTL — captures organic referrals without requiring immediate signup
- Async reward issuance with fraud hold — prevents paying out before fraud is detected
- Device fingerprint + IP check for self-referral detection — primary fraud signal
- Velocity limits on code use and signups per IP — blocks mass fake account creation
- Two-sided rewards — incentivizes both sharing and completing the qualifying action
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”How do you generate and track unique referral codes?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Referral codes must be unique, short (for sharing), and not easily guessable. Generation: random 8-character alphanumeric code from [A-Z2-9] (exclude 0, O, 1, I to avoid confusion): 32^8 ≈ 1 trillion combinations, negligible collision probability at 100M users. Generate on first request and cache permanently in Redis: GET referral_code:{user_id} → if miss, generate, SET without expiry, store in DB. Alternatively, encode user_id in base62 for short deterministic codes (no collision risk, but reveals approximate user count). URL: https://app.example.com/signup?ref=ABC123 or a short link (https://app.example.com/r/ABC123). Link tracking: on click, create a ReferralClick record (code, ip, device_fingerprint, user_agent, click_at) before redirecting. This enables click funnel analysis (clicks → signups → qualified) and fraud detection (N clicks from same IP).”}},{“@type”:”Question”,”name”:”How does attribution work when a user clicks multiple referral links?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”A user might click a referral link from User A on Monday and another from User B on Thursday before signing up. Attribution determines who gets credit. Last-touch attribution (most common): the most recent referral link click gets credit. Implementation: when the user clicks a referral link, store the code in a cookie: Set-Cookie: ref=ABC123; Max-Age=2592000 (30 days). Subsequent clicks overwrite the cookie. On signup, read the cookie to get the referring code. First-touch attribution: store only the first code (cookie is not overwritten if already set). Multi-touch: distribute reward across all referrers proportionally. For most B2C referral programs, last-touch is standard. Server-side: if the user is already logged in when they click a referral link, attribute directly to their account rather than relying on cookies. Store click history in the DB for dispute resolution.”}},{“@type”:”Question”,”name”:”How do you detect and prevent referral fraud?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Referral fraud includes self-referrals (one person creating multiple accounts), reward farming (organized groups cross-referring), and fake account farms. Detection signals: (1) Same IP address for referrer and referee at signup — strongest signal for self-referral. Block if same /24 IP subnet. (2) Same device fingerprint (browser fingerprint, mobile device ID) — catches the same physical device creating multiple accounts. (3) Same payment method — same credit card or bank account as the referrer at first purchase. (4) Disposable email address — block common disposable email domains. (5) Phone verification — require phone number for reward qualification. Hard to fake at scale. (6) Velocity limits — max N signups from same IP per day, max M uses of a single referral code per day. (7) Social graph analysis — a tight cluster of users all referring each other is suspicious. Fraud holds: issue rewards only after a 7-30 day hold period. If fraud is detected during the hold, cancel the reward before it is issued.”}},{“@type”:”Question”,”name”:”How do you implement two-sided referral rewards that are fraud-resistant?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Two-sided rewards: referrer gets a reward AND referee gets a reward (e.g., "Give $10, Get $10"). This increases conversion compared to one-sided programs. The referee reward is the main fraud vector — fraudsters create fake accounts to claim the referee reward. Fraud-resistant two-sided design: (1) Gate referee reward on a meaningful qualifying action — not just signup, but first purchase, account verification, or completing a profile. This raises the cost of fraud. (2) Issue referee reward after a hold period — give fraud detection time to flag the account before the reward is applied. (3) Non-transferable rewards — issue credits/discounts that can only be used within the platform (not cash). Harder to monetize from a fake account. (4) Tie reward to a verified payment method — require a credit card on file before issuing cash rewards. (5) Cap referee reward value — a $5 credit isn't worth the effort of creating a fake account.”}},{“@type”:”Question”,”name”:”How do you handle the qualifying action event and reward issuance reliably?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”The qualifying action (first purchase, account verification) triggers reward issuance. This must be reliable — if the system crashes between recording the qualifying action and issuing the reward, the user should not be rewarded twice or not at all. Approach: (1) Publish a QualifyingActionCompleted event to Kafka when the action is recorded. The referral service consumes this event. (2) Idempotency: use the referral_id as the idempotency key for reward issuance. If the event is delivered twice (Kafka at-least-once), the second processing finds referral.status already set to QUALIFIED and skips. (3) The referral service updates referral.status = QUALIFIED and creates ReferralReward records in a single DB transaction. (4) A background job processes PENDING ReferralReward records, applies them to the user's account (credit balance, discount code, etc.), and marks them ISSUED. If the job fails, it retries — idempotency prevents double-crediting.”}}]}
Shopify system design covers referral and growth programs. See common questions for Shopify interview: referral and growth system design.
Uber system design covers referral programs and driver/rider incentives. Review patterns for Uber interview: referral and incentive system design.
Airbnb system design covers referral growth systems. See design patterns for Airbnb interview: referral and growth system design.