Skip to main content

Context

Referrals are the highest-leverage acquisition mechanism a loyalty program has, and the easiest to farm. Every design choice here is really an anti-fraud choice. The one that matters most: the reward is released by a qualifying event, not by signup. If signing up pays, the program pays for fake accounts, and it will — reliably, within days of launch. Tying release to a first paid purchase means the fraudster must spend real money to extract a reward worth less than what they spent. DEC-H7 constrains the qualifying event too: it is configured per tenant from their installed taxonomy, because “became a real customer” means an invoice paid for a subscription business and a first order for retail.

Scope (normative)

  • loyalty.referral_codes: one durable code per member per program, with a shareable link.
  • loyalty.referral_conversions: the referred contact, its state, and the qualifying event.
  • Double-sided reward: configurable amounts for referrer and referred, released on qualification.
  • Anti-fraud: self-referral detection, velocity limits, and device/IP heuristics recorded as flags.
  • loyalty.referral_fraud_flags for review rather than silent rejection.
  • Attribution window between click and qualification.

Non-scope (normative)

  • Multi-level or pyramid structures. Explicitly never — a referrer earns from their own referrals only. This is a product decision, not a phase.
  • Sending the invitation. The link is produced here; delivering it is messaging.
  • Landing pages and the sharing UI, which belong to frontend/portal and the widget.

Behaviour (normative)

  1. A member’s referral code is stable for the life of the program. Regenerating it would break every link already shared.
  2. A conversion moves pending → qualified → rewarded, or pending → rejected. Rewards are released only on the tenant-configured qualifying event, which defaults to a first paid purchase and is never a signup.
  3. Self-referral is blocked at conversion, matched on the identity signals core already resolves — never on email string equality alone, which is trivially defeated.
  4. Velocity limits are per referrer and per window, configurable. Exceeding one flags for review; it does not silently reject. A false positive that silently swallows a legitimate referral is worse than one that queues it for a human.
  5. Device and IP heuristics are recorded as flags with their evidence, never as an automatic verdict. A shared household IP is normal.
  6. Both rewards are released in one transaction. Rewarding one side only is FORBIDDEN.
  7. The attribution window is configurable per program. A conversion outside it is recorded and rejected with that reason, not dropped.
  8. A rejected or flagged conversion is never deleted — the evidence is the point.

Data (normative)

API (normative)

Events (normative)

loyalty.referral.link_created and loyalty.referral.converted, both available as outgoing webhooks. Reward release emits loyalty.points.earned through the ledger service.

Acceptance criteria (normative)

  1. A signup with no qualifying event releases no reward, and the conversion sits at pending.
  2. The qualifying event releases both rewards in one transaction; an induced failure on the second rolls back the first.
  3. Self-referral via a matching resolved identity is blocked even when the email strings differ.
  4. Exceeding the velocity limit flags for review and leaves the conversion pending — it is not rejected and not silently dropped.
  5. A conversion arriving after the attribution window is recorded with reason WINDOW_EXPIRED.
  6. Replaying the qualifying event does not release a second reward.
  7. Negative: no configuration allows a referrer to earn from a referral of a referral.

Execution

Asynchronous pipeline: attribution is synchronous and cheap, qualification is driven by the rules engine consuming the qualifying event. Fraud heuristics run in the worker, never on the hot path.

Open questions

None outstanding. Every question this spec carried was answered in the consolidated register (../../../design/open-questions-v1.md, v1.1) and folded into the normative sections above.

Changelog

Delivery record

Not implemented yet.