MMOLove Docs
Referral Kit

REST reference

POST /api/referral/events — the authoritative referral event-ingest contract.

The referral event-ingest endpoint is the source of truth for the Referral Kit. Your server reports lifecycle events here as referees progress.

POST /api/referral/events
Content-Type: application/json
X-MMOLove-Signature: t=<unix>,v1=sha256=<hex>[,kid=<key-id>]

Events

Your kit reports two forward events:

eventWhen you send itEffect
registeredThe referee creates an accountMints the referral anchor (first-touch). Requires referee_identity.
qualifiedThe referee hits your milestoneAdvances the referral; credits the referrer. Reward time.

A third value, reversed (owner reversal), is accepted and modelled in the state machine, but the current endpoint only acts on registered / qualified.

Request body

FieldTypeRequiredNotes
eventstringyesregistered | qualified | reversed.
tokenstringyesThe mmref token your registration page captured.
server_idstringyesYour MMOLove server id (also selects the signing secret).
referee_identitystringon registeredThe referee's stable identity (account id / username). The anchor key.
server_event_idstringyesYour idempotency key for this event (see Idempotency).
tsnumberrecommendedUnix seconds — your event time (for your own logs/ordering).
testbooleannotrue dry-runs: fully signature-verified, but never writes or advances state. Returns { ok: true, test: true }.

The endpoint reads the raw request body first and verifies the signature over those exact bytes before parsing — so the body you sign must be byte-identical to the body you send.

Signing (X-MMOLove-Signature)

Every request is authenticated with an HMAC-SHA256 over the raw body, keyed by your per-server secret (rotate it any time from the Integration tab).

Header format:

X-MMOLove-Signature: t=<unix>,v1=sha256=<hex>[,kid=<key-id>]
  • t — Unix seconds at signing time. Drives the replay window.
  • v1sha256=<hex>, where <hex> is HMAC_SHA256(secret, "<t>.<rawBody>"). Note the MAC is computed over the string <t>.<rawBody> (the timestamp, a literal dot, then the raw JSON body).
  • kidoptional key id, for secret rotation. Carried through to your side; the mapping of kid → secret is yours to manage.

The #1 integration bug: signing a re-serialized object instead of the exact bytes you send. Re-serialization changes whitespace / key order and the MAC will (correctly) fail. Build the body string once, sign that string, and send that same string.

Replay window

The signature timestamp t must be within ±5 minutes of server time. The MAC is verified before the clock check (so a forged timestamp can't be used to probe the window). Outside the window → 401 (stale).

Idempotency

Deduplication is on the tuple (token, type, server_event_id) — never on token alone (so registered and qualified can both land for one referral). The event row is inserted before any processing, so a duplicate is absorbed and returns 200 (never a 4xx). Safe to retry: reuse the same server_event_id and a replay is a no-op.

First-touch is enforced too: the anchor is minted once per (server_id, referee_identity). A second referrer registering the same referee is acknowledged with 200 and ignored ({ ignored: "first_touch_conflict" }).

Status codes

CodeMeaning
200OK, or an idempotent duplicate / first-touch no-op. Body carries ok: true (+ referral_id, state, or duplicate/test/ignored).
400Malformed — bad/missing header, unparseable body, missing/invalid fields, or registered without referee_identity.
401Signature rejected — bad signature or stale timestamp (outside the ±5-min window).
404Unknown server, referrals not enabled for it, no secret configured, or unknown mmref token.
422Invalid state transition (e.g. qualified before registered). Body carries from + event.
500Internal error.

Worked example

For a body of {"event":"registered","token":"mmref_abc","server_id":"srv_123","referee_identity":"player42","server_event_id":"evt-1","ts":1733500000} signed at t=1733500000 with secret s3cr3t, compute:

mac = HMAC_SHA256("s3cr3t", "1733500000.<rawBody>")
header = "t=1733500000,v1=sha256=" + mac

Skip the boilerplate with an SDK (npm i @mmolove/referral, pip install mmolove-referral, dotnet add package MMOLove.Referral, or the single-file PHP drop-in), or see the SDK guides for complete copy-paste implementations in PHP, Node/JS, Python, .NET/C#, and cURL. The Signing & security page covers the HMAC scheme in depth, and this contract is also published as a downloadable OpenAPI 3.1 spec — see the API reference.

On this page