Concepts
The referral lifecycle and state machine, the durable anchor, first-touch attribution, the attribution window, and who owns what.
The Referral Kit is built on a few small ideas. Understand these and the REST contract reads like common sense.
The conduit model — who owns what
MMOLove is the conduit. The split never moves:
| MMOLove (us) | Server owner (you) |
|---|---|
Owns the /r/<server> link + the signed mmref token | Defines what "qualified" means (level 10, 2h played, first purchase — your call) |
| Tracks the lifecycle: clicks, registrations, milestones | Reports lifecycle events from your kit |
| Mints + holds the per-server signing secret | Grants the in-game reward |
| Surfaces analytics, the funnel, and the referrers leaderboard | Resolves who to reward via your chosen identity field |
We do the attribution; you own the payout. MMOLove never touches your economy and never hands out rewards.
The lifecycle
A referral moves through a small state machine. Two inbound events drive the
forward path — registered and qualified — and a couple of states model
reversal and expiry.
registered qualified
┌─────────┐ (your kit) ┌──────────────┐ (your kit) ┌───────────┐
│ clicked │ ───────────────▶ │ registered │ ─────────────▶ │ qualified │
└─────────┘ └──────────────┘ └───────────┘
│ │ │ │
│ /r link followed │ │ reversed (owner) │
│ (mmref minted) │ ▼ │
│ │ ┌──────────┐ │
│ └──▶│ reversed │◀─────────────────┘
│ └──────────┘
│
│ maturation window elapses
▼
┌─────────┐
│ expired │ rejected (anti-abuse) is also terminal
└─────────┘| State | Meaning | How it's reached |
|---|---|---|
clicked | The /r/<server> link was followed — a click row + mmref token exist. | Player follows the share link. |
registered | The anchor is minted; referrer ↔ referee are bound. | Your registered event. |
qualified | The referee hit your milestone — reward time. | Your qualified event. |
reversed | An owner reversal (refund / chargeback / abuse). | Your reversed event (modelled; see note). |
expired | The attribution window elapsed before qualification. | Time-driven (modelled). |
rejected | Anti-abuse rejection. | System (modelled). |
The endpoint accepts registered, qualified, and reversed. The current
phase acts on registered and qualified; reversed and the time-driven
expired / rejected states are modelled so the table is complete. expired and
rejected are terminal — no inbound event advances them.
Legal transitions
The state machine is strict, so out-of-order events fail loudly instead of corrupting attribution:
registeredfromclicked→ advance toregistered(mints the anchor).registeredwhen alreadyregistered/qualified→ no-op (idempotent).qualifiedfromregistered→ advance toqualified.qualifiedwhen alreadyqualified→ no-op.qualifiedfromclicked(no anchor yet) → invalid →422. You must sendregisteredfirst.reversedfromregistered/qualified→ advance toreversed.- Anything into a terminal state → invalid (a repeat of the same terminal event is a no-op).
See Errors & troubleshooting for how each maps to a status code.
The durable anchor
When your registered event lands, MMOLove mints a long-lived referral_id
— the anchor. Every later event (qualified, reversed) keys off it.
The anchor is deliberately decoupled from the click token's expiry. The
mmref token in the link is signed and time-bounded; the anchor it mints is
durable, so a referee who registers near the edge of the click window still
qualifies weeks later without the original token being "live."
The anchor row carries the referrer, the referee identity, the originating token,
the current state, and the registered_at / qualified_at timestamps.
First-touch — one referrer per referee
Attribution is first-touch: the first referrer to register a given referee
wins, permanently. The anchor is unique per (server_id, referee_identity), so:
- The first
registeredfor a referee mints the anchor and binds that referrer. - A different referrer sending
registeredfor the same referee collides on that uniqueness and is acknowledged with200and ignored ({ "ignored": "first_touch_conflict" }). The original anchor is never overwritten.
This is why referee_identity must be stable — the same player must always
map to the same identity value, or first-touch can't hold.
Pick an identity that never changes for a player (an internal account id is ideal). A renamable display name will break first-touch the day a player renames.
The attribution window — maturation_days
Each server config carries a maturation_days window (the industry default is
90 days; configurable 1–730 on the Referrals tab). It's the period in which a
registered referral can still mature to qualified and count. Once it elapses
without qualification, the referral is considered expired.
Set it to match how long it realistically takes a new player to reach your milestone — generous enough that genuine referrals land, tight enough to keep the funnel honest.
Idempotency & dedup
Every event you send carries a server_event_id — your idempotency key.
Dedup is on the tuple (token, type, server_event_id), never on token
alone, so registered and qualified can both land for one referral. Resending
the exact same event is absorbed and returns 200. This makes retries completely
safe — see Signing & security.
Anti-abuse caps
The config also supports anti-abuse caps (per referrer, per IP, per month;
0 = unlimited) and a display-only reward_description. These are owner-set on
the Referrals tab. As with everything else: MMOLove records and surfaces; you
decide and grant.