MMOLove Docs
Referral KitSDK guides

PHP

A complete, copy-paste PHP integration for the Referral Kit — a single-file drop-in in the vote-callback style, with signing, error handling, and a full worked example.

This is the complete PHP integration — one self-contained file you can drop next to your existing MMOLove vote-callback handler. It captures the mmref token, mints the anchor with registered, and credits the referrer with qualified, all correctly signed.

PHP is the first-class path because it matches the vote-callback pattern you already run. If you've wired the heart/reward callback, this will feel identical.

The official mmolove-referral.php is a single-file drop-in — no Composer needed. Vendor the file, require it, and call one function:

require 'mmolove-referral.php';

// referee_identity is REQUIRED on registered — the first-touch anchor:
$res = mmolove_referral_send($MMOLOVE_SECRET, $MMOLOVE_SERVER_ID, 'registered', $mmref, [
    'referee_identity' => 'player-7842',
    'server_event_id'  => 'reg-7842',   // idempotency key, stable across retries
]);

// When they hit your "qualified" bar:
$res = mmolove_referral_send($MMOLOVE_SECRET, $MMOLOVE_SERVER_ID, 'qualified', $mmref, [
    'server_event_id' => 'qual-7842',
]);

if ($res['status'] !== 200) {
    error_log("referral send failed: {$res['status']} " . json_encode($res['body']));
}

mmolove_referral_send($secret, $serverId, $event, $token, $opts) returns ['status' => int, 'body' => mixed, 'raw_body' => string, 'header' => string, 'error' => string|null]. A 200 covers new events and idempotent duplicates (same server_event_id). Need to sign without sending? Use mmolove_referral_build_body(...) + mmolove_referral_sign(...) and POST the exact raw_body bytes.

The rest of this page is the from-scratch version of that same file, if you'd rather paste it inline and tweak it.

What you need

  • Your signing secret and server id from the Referrals tab.
  • A registration page that can read a query param / cookie.
  • A milestone hook (where you already decide a player has "made it").
<?php
// config — load from env / secrets manager; NEVER hardcode in a public repo.
const MMOLOVE_ENDPOINT = 'https://mmolove.com/api/referral/events';
$MMOLOVE_SECRET    = getenv('MMOLOVE_REFERRAL_SECRET');   // from the Referrals tab
$MMOLOVE_SERVER_ID = getenv('MMOLOVE_SERVER_ID');         // your server id

The drop-in helper

mmolove-referral.php — the whole integration. Build the body once, sign those exact bytes, send those exact bytes.

<?php
/**
 * mmolove-referral.php — single-file Referral Kit client (vote-callback style).
 *
 * Reports a referral lifecycle event to MMOLove with an HMAC-signed body.
 * @return array{status:int, body:string}
 */
function mmolove_send_event(string $secret, string $serverId, string $event, array $extra): array
{
    // Build the body ONCE. We sign these exact bytes and send these exact bytes.
    $payload = array_merge([
        'event'     => $event,            // 'registered' | 'qualified'
        'server_id' => $serverId,
        'ts'        => time(),            // your event time (your logs/ordering)
    ], $extra);
    $raw = json_encode($payload, JSON_UNESCAPED_SLASHES);

    // HMAC-SHA256 over "<t>.<rawBody>" (timestamp, a dot, then the raw body).
    $t   = time();
    $mac = hash_hmac('sha256', $t . '.' . $raw, $secret);   // lowercase hex
    $sig = "t={$t},v1=sha256={$mac}";

    $ch = curl_init(MMOLOVE_ENDPOINT);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $raw,                      // <-- the SAME bytes we signed
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            "X-MMOLove-Signature: {$sig}",
        ],
    ]);
    $body   = curl_exec($ch);
    $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $err    = curl_error($ch);
    curl_close($ch);

    if ($body === false) {
        // Transport failure (DNS/timeout). Treat like a 5xx: retry with backoff.
        return ['status' => 0, 'body' => $err];
    }
    return ['status' => $status, 'body' => $body];
}

1. Capture the mmref token at registration

When MMOLove redirects a referred player to your registration page it appends ?mmref=<token> and also drops a first-party mmref cookie. Read it (query first, cookie fallback) and persist it against the new account.

<?php
// On your registration page, BEFORE creating the account.
$mmref = $_GET['mmref'] ?? ($_COOKIE['mmref'] ?? null);
if ($mmref !== null && $mmref !== '') {
    // Store it on the new account row so you can echo it back below.
    // e.g. INSERT INTO accounts (..., mmref) VALUES (..., :mmref)
    $newAccount->mmref = $mmref;
}

2. Report registered — mint the anchor

Right after the account is created, fire registered. This binds the referrer to this referee (first-touch) and mints the durable referral_id. Use a stable identity (an internal account id, not a renamable display name).

<?php
if (!empty($newAccount->mmref)) {
    $res = mmolove_send_event($MMOLOVE_SECRET, $MMOLOVE_SERVER_ID, 'registered', [
        'token'            => $newAccount->mmref,            // the captured token
        'referee_identity' => (string) $newAccount->id,      // STABLE id
        'server_event_id'  => 'reg-' . $newAccount->id,      // idempotency key
    ]);
    mmolove_handle_result($res);   // see error handling below
}

3. Report qualified at your milestone

When the referee reaches your definition of "qualified" (level 10, 2h played, first purchase — your call), fire qualified. Reuse the same token; give it a new server_event_id. Then grant the in-game reward to the referrer.

<?php
function on_player_qualified(object $player): void
{
    global $MMOLOVE_SECRET, $MMOLOVE_SERVER_ID;

    if (empty($player->mmref)) {
        return;   // not a referred player — nothing to report
    }

    $res = mmolove_send_event($MMOLOVE_SECRET, $MMOLOVE_SERVER_ID, 'qualified', [
        'token'           => $player->mmref,                 // SAME token as register
        'server_event_id' => 'qual-' . $player->id,          // NEW idempotency key
    ]);

    if (mmolove_handle_result($res)) {
        // MMOLove credited the referrer. Now YOU grant the reward in-game.
        grant_referrer_reward_for($player);
    }
}

Error handling

Decode the response and branch on the status. 200 covers several success shapes (advanced, duplicate, first-touch-ignored) — treat them all as "stop retrying."

<?php
/** @return bool true if the event was accepted (a 200 of any flavour). */
function mmolove_handle_result(array $res): bool
{
    $status = $res['status'];
    $data   = json_decode($res['body'] ?: 'null', true);

    if ($status === 200) {
        // {ok:true,...} — advanced, OR duplicate, OR first_touch_conflict, OR test.
        // All are terminal; do NOT retry.
        return true;
    }

    if ($status === 0 || $status >= 500) {
        // Transport/5xx — safe to retry with backoff (same server_event_id).
        mmolove_enqueue_retry($res);
        return false;
    }

    // 4xx — a bug in the request; retrying won't help. Log and fix.
    $err = $data['error'] ?? 'unknown';
    error_log("[mmolove] event rejected ({$status}): {$err}");
    return false;
}
You getWhat it meansDo
200Accepted (incl. duplicate / first-touch / test)Stop. On qualified, grant the reward.
400Malformed requestFix the payload — see Errors.
401Bad signature or stale tAlmost always the raw-body bug; or wrong/old secret; or clock drift.
404Unknown server/token or referrals offCheck server_id, enable referrals, verify the token.
422Invalid transition (e.g. qualified-first)Send registered before qualified.
0 / 5xxTransport / server errorRetry with backoff (idempotent via server_event_id).

Test it first

Add 'test' => true to dry-run — fully signature-verified, never written:

<?php
$res = mmolove_send_event($MMOLOVE_SECRET, $MMOLOVE_SERVER_ID, 'registered', [
    'token'            => '<mmref>',
    'referee_identity' => '<player>',
    'server_event_id'  => 'test-1',
    'test'             => true,
]);
// Expect: status 200, body {"ok":true,"test":true}

See Testing & sandbox for the full smoke-test sequence and the dashboard Send test event button.

Keep $MMOLOVE_SECRET server-side. Never expose it in client/browser code, and never commit it. If it leaks, rotate it on the Referrals tab (which invalidates the old one immediately).

Next

On this page