Python
A complete, copy-paste Python integration for the Referral Kit — stdlib only (hmac + hashlib + urllib), with signing, error handling, and a full Flask example.
A complete Python integration: capture the mmref token, mint the anchor with
registered, credit the referrer with qualified, all correctly signed.
Use the package (recommended)
The official mmolove-referral package is zero-dependency (stdlib
only) and does the signing + POST for you:
pip install mmolove-referralfrom mmolove_referral import ReferralClient
client = ReferralClient(
server_id=os.environ["MMOLOVE_SERVER_ID"],
secret=os.environ["MMOLOVE_REFERRAL_SECRET"], # Referrals tab
# endpoint defaults to https://mmolove.gg/api/referral/events
)
# referee_identity is REQUIRED on registered — the first-touch anchor:
client.registered(token, referee_identity="player-7842", server_event_id="reg-7842")
# When they hit your "qualified" bar:
res = client.qualified(token, server_event_id="qual-7842")
if not res.ok:
print(res.status, res.body)registered / qualified return a SendResult (.status, .ok, .body); a
200 covers new events and idempotent duplicates. Non-2xx is surfaced on the
result (not raised); only a transport failure raises RuntimeError. Need to sign
without sending? Use client.sign(...) or the pure sign_referral_body(...). Or
just drop mmolove_referral.py in (no install needed).
The rest of this page is the from-scratch path — a stdlib-only module if you'd rather not add the package. It's exactly what the SDK wraps.
What you need
- Your signing secret and server id from the Referrals tab.
- A registration route that can read a query param / cookie.
- A milestone hook (where you decide a player has "qualified").
# config.py — load from env; NEVER hardcode secrets.
import os
MMOLOVE_ENDPOINT = "https://mmolove.com/api/referral/events"
MMOLOVE_SECRET = os.environ["MMOLOVE_REFERRAL_SECRET"] # from the Referrals tab
MMOLOVE_SERVER_ID = os.environ["MMOLOVE_SERVER_ID"] # your server idThe client
mmolove_referral.py — the whole integration. Build the body once, sign those
exact bytes, send those exact bytes.
import hashlib
import hmac
import json
import time
import urllib.error
import urllib.request
ENDPOINT = "https://mmolove.com/api/referral/events"
def mmolove_send_event(secret: str, server_id: str, event: str, extra: dict) -> tuple[int, str]:
"""Report a referral lifecycle event to MMOLove with an HMAC-signed body.
Returns (status_code, response_body). status_code 0 means a transport error.
"""
# Build the body ONCE. Sign these exact bytes; send these exact bytes.
payload = {"event": event, "server_id": server_id, "ts": int(time.time()), **extra}
raw = json.dumps(payload, separators=(",", ":")) # compact, stable
# HMAC-SHA256 over "<t>.<rawBody>" (timestamp, a dot, then the raw body).
t = int(time.time())
mac = hmac.new(secret.encode(), f"{t}.{raw}".encode(), hashlib.sha256).hexdigest()
sig = f"t={t},v1=sha256={mac}"
req = urllib.request.Request(
ENDPOINT,
data=raw.encode(), # the SAME bytes we signed
headers={"Content-Type": "application/json", "X-MMOLove-Signature": sig},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=10) as res: # noqa: S310 (trusted)
return res.status, res.read().decode()
except urllib.error.HTTPError as e:
# 4xx / 5xx come back here — read the JSON error body.
return e.code, e.read().decode()
except urllib.error.URLError as e:
# Transport failure (DNS/timeout). Treat like a 5xx: retry with backoff.
return 0, str(e)1. Capture the mmref token at registration
MMOLove redirects referred players to your registration page with ?mmref=<token>
(and a first-party mmref cookie as fallback). Read it and persist it on the new
account.
from flask import request
# In your registration view, BEFORE creating the account.
def capture_mmref():
return request.args.get("mmref") or request.cookies.get("mmref")
@app.post("/register")
def register():
mmref = capture_mmref()
account = create_account(**request.form, mmref=mmref) # store it
# ... continue to step 22. Report registered — mint the anchor
Right after the account exists, fire registered. This binds the referrer to this
referee (first-touch). Use a stable identity (internal account id), not a
renamable name.
from mmolove_referral import mmolove_send_event
from config import MMOLOVE_SECRET, MMOLOVE_SERVER_ID
if account.mmref:
status, body = mmolove_send_event(MMOLOVE_SECRET, MMOLOVE_SERVER_ID, "registered", {
"token": account.mmref, # the captured token
"referee_identity": str(account.id), # STABLE id
"server_event_id": f"reg-{account.id}", # idempotency key
})
handle_result(status, body) # see error handling below3. Report qualified at your milestone
When the referee reaches your milestone, fire qualified with the same
token and a new server_event_id. Then grant the in-game reward.
def on_player_qualified(player):
if not player.mmref:
return # not a referred player
status, body = mmolove_send_event(MMOLOVE_SECRET, MMOLOVE_SERVER_ID, "qualified", {
"token": player.mmref, # SAME token as register
"server_event_id": f"qual-{player.id}", # NEW idempotency key
})
if handle_result(status, body):
# MMOLove credited the referrer. Now YOU grant the reward in-game.
grant_referrer_reward(player)Error handling
import json
def handle_result(status: int, body: str) -> bool:
"""Return True if the event was accepted (any 200)."""
try:
data = json.loads(body)
except ValueError:
data = {}
if status == 200:
# {"ok": true, ...} — advanced, duplicate, first_touch_conflict, or test.
# All terminal; do NOT retry.
return True
if status == 0 or status >= 500:
enqueue_retry(status, body) # backoff; same server_event_id
return False
# 4xx — a request bug; retrying won't help.
print(f"[mmolove] event rejected ({status}): {data.get('error', body)}")
return False| You get | Meaning | Do |
|---|---|---|
200 | Accepted (incl. duplicate / first-touch / test) | Stop. On qualified, grant the reward. |
400 | Malformed request | Fix the payload — Errors. |
401 | Bad signature or stale t | Usually the raw-body bug; or wrong/old secret; or clock drift. |
404 | Unknown server/token, or referrals off | Check server_id, enable referrals, verify the token. |
422 | Invalid transition (e.g. qualified-first) | Send registered first. |
0 / 5xx | Transport / server error | Retry with backoff (idempotent via server_event_id). |
Test it first
status, body = mmolove_send_event(MMOLOVE_SECRET, MMOLOVE_SERVER_ID, "registered", {
"token": "<mmref>",
"referee_identity": "<player>",
"server_event_id": "test-1",
"test": True, # dry run — verified, not written
})
# Expect: 200, {"ok":true,"test":true}See Testing & sandbox for the full sequence and the dashboard Send test event button.
MMOLOVE_SECRET is server-side only. Never ship it to a client/browser, and never
commit it. If it leaks, rotate it on the Referrals tab.
Next
Node / JS
A complete, copy-paste Node.js integration for the Referral Kit — one module using fetch + node:crypto, with signing, error handling, and a full Express example.
.NET / C#
A complete, copy-paste .NET integration for the Referral Kit — HMACSHA256 + HttpClient, with signing, error handling, and a full ASP.NET example.