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>| Param | Meaning |
|---|---|
username | The voter's in-game name — the reward recipient. |
ts | Unix seconds at delivery time. |
sig | HMAC_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 hexThis 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
Signing & verification
The exact X-MMOLove-Signature HMAC scheme for reward callbacks — verify over the raw body, the header format, a worked example, and a replay-window recommendation.
Testing & self-verify
Prove your reward-callback wiring with the dashboard "Send test callback" button and the delivery log — without waiting for a real vote.