Referral System Low-Level Design

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

  1. Referrer requests their referral code: GET /referral/code → generate or return existing code
  2. Referrer shares URL: https://app.example.com/signup?ref=ABC123
  3. Prospect clicks link → server stores referral_code in cookie/session (TTL=30 days)
  4. Prospect signs up → read referral_code from cookie → create Referral record (status=PENDING)
  5. 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.

Scroll to Top