MMOLove Docs
Reward Callbacks — guides

Legacy GET mode

The older GET-with-query-params reward callback for legacy vote scripts — its URL shape, the username|ts signature, and how to verify it.

Legacy GET mode is a compatibility path for older vote-callback scripts that expect a plain GET request with the reward details in the query string, rather than a signed JSON POST. New integrations should use the default signed POST — it carries far more context (heart_id, period, streak_day) and is harder to forge.

Only enable legacy GET if you're wiring a pre-existing script that already speaks this shape. For anything new, leave the Use legacy GET callback toggle off and use the POST callback.

When it fires

With Use legacy GET callback enabled on the Integration tab, a counted heart triggers a GET to your callback URL with three query params appended:

GET https://your-server.example/callback?username=<name>&ts=<unix>&sig=<hex>
ParamMeaning
usernameThe voter's in-game name — the reward recipient.
tsUnix seconds at delivery time.
sigHMAC_SHA256(secret, "<username>|<ts>") in lower-case hex. The signature.

There is no request body and no X-MMOLove-Signature header in legacy mode — the signature is the sig query param, and it's computed differently from the POST signature (see below).

The legacy signature

The legacy sig is an HMAC-SHA256 over "<username>|<ts>" — the username, a literal pipe (|), then the timestamp — in lower-case hex:

sig = HMAC_SHA256(secret, username + "|" + ts)   // lower-case hex

This is not the same string as the POST signature. The POST callback signs "<t>.<rawBody>" (dot-separated, over the JSON body); legacy GET signs "<username>|<ts>" (pipe-separated, no body). Don't reuse a POST verifier for legacy GET.

Verifying a legacy callback

Recompute the MAC from the username + ts query params and compare it constant-time to sig:

<?php
$username = $_GET['username'] ?? '';
$ts       = $_GET['ts'] ?? '';
$sig      = $_GET['sig'] ?? '';

$expected = hash_hmac('sha256', $username . '|' . $ts, $SECRET);  // lower-case hex
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit;
}
// (Recommended) reject if abs(time() - (int)$ts) is more than a few minutes.
grant_reward($username);
http_response_code(200);

What you return

Same as the POST callback: return a 2xx once you've accepted the vote and MMOLove marks the delivery delivered; any non-2xx (or timeout) is retried with backoff. See Errors, retries & delivery.

Legacy GET has no heart_id, so you can't dedupe on it. To keep retries safe, dedupe on a (username, ts) pair instead — the same delivery always carries the same ts.

Migrating off legacy GET

Moving to the signed POST callback is a clean upgrade:

Add a POST handler

Stand up a handler that reads the raw body, verifies X-MMOLove-Signature, and rewards username — see the handler guides.

Point the callback at it

Set the Reward callback URL to the new POST endpoint and turn off the legacy GET toggle on the Integration tab.

Send a test callback

Click Send test callback and confirm a signed heart.test POST arrives and verifies. You now have heart_id, period, and streak_day to work with.

See also

On this page