Log inSign up

Reward callbacks

The core vote→reward webhook — a player hearts your server, MMOLove POSTs a signed heart.counted event, you grant the in-game reward.

The reward callback is the core vote→reward loop. When a player gives your server a heart, MMOLove sends a signed POST to your callback URL so you can grant an in-game reward — more votes, more rewards, more reasons to vote. This is the webhook the heart button on your listing fires.

This page is the overview + the authoritative payload and signing reference. For copy-paste handlers, the legacy GET mode, self-verification, and a full error table, follow the section pages below.

New here? The Quickstart is the 5-minute path: set your callback URL, copy your secret, send a test callback, watch it land in the delivery log. Then drop in your stack's handler.

How it works

A player hearts your server

On your MMOLove listing a player taps the heart. MMOLove records the vote (with fraud scoring + a 24-hour per-identity cooldown) and, if it counts, queues a delivery for your server.

MMOLove POSTs a signed heart.counted

A background worker delivers a signed JSON POST to your configured callback URL. The body names the player (their in-game username), the vote, and the player's current daily streak so you can scale the reward.

You verify the signature

Recompute the HMAC over the raw body and compare it to the X-MMOLove-Signature header. Only then trust the request — it proves the call genuinely came from MMOLove (and wasn't replayed or forged).

You grant the in-game reward

Look up the username, hand out the loot, return a 2xx. MMOLove marks the delivery delivered. A non-2xx (or a timeout) is retried with backoff. You own the economy; MMOLove just tells you who voted.

Skip the verify boilerplate with an SDK: npm i @mmolove/callback, pip install mmolove-callback, dotnet add package MMOLove.Callback, or the single-file PHP mmolove-callback.php drop-in — each validates the signature in one call. You don't need one; any language with an HMAC-SHA256 can verify directly.

Set it up

Configure the callback on your server's dashboard under the Integration tab:

  • Reward callback URL — a public https:// endpoint, e.g. https://your-server.example/mmolove/callback. We POST every heart.counted here. (Private / loopback / link-local URLs are rejected for SSRF safety.)
  • Signing secret — click Rotate secret to mint one. It's shown once; copy it and store it where your callback handler can read it (env var / secrets manager — never the browser or game client).
  • Legacy GET callback — leave off for new integrations. It exists only for older vote-callback scripts that expect a GET with query params. See Legacy GET mode.

Then click Send test callback to fire a signed heart.test at your endpoint and confirm the wiring before real votes arrive.

Reward callbacks

When a heart is counted, MMOLove delivers a signed JSON POST to your callback URL:

POST /your/callback/path
Content-Type: application/json
X-MMOLove-Event: heart.counted
X-MMOLove-Signature: t=<unix>,v1=<hex>

The body (heart.counted) is:

{
  "event": "heart.counted",
  "server_id": "8f0e…-server-uuid",
  "username": "PlayerOne",
  "heart_id": "1a2b…-heart-uuid",
  "period": "2026-06",
  "timestamp": 1733500000,
  "streak_day": 7,
  "loyalty_score": 240
}
FieldTypeAlways presentMeaning
eventstringyesheart.counted for a real vote; heart.test for the dashboard's Send test callback.
server_idstringyesYour MMOLove server id (UUID). The server the heart was cast for.
usernamestringyesThe voter's in-game name — the reward recipient. This is the value the player typed when voting.
heart_idstringyesThe vote's unique id (UUID). Use it as an idempotency key so a retry never double-rewards.
periodstringyesThe vote's ranking period, YYYY-MM (UTC month), e.g. 2026-06.
timestampnumberyesUnix seconds at delivery time. Also the t used in the signature.
streak_daynumbernoThe voter's current daily vote streak (in days) for this server at delivery time, so you can grant escalating rewards (bigger bonuses at day 7, 30…). Omitted when the streak is unavailable — treat a missing field as "no streak info", not zero. Present only for voters signed in to MMOLove (streaks are account-based).
loyalty_scorenumbernoThe voter's cumulative Loyalty Score with this server — earned per counted heart (+10) and streak milestones — so you can reward your most devoted players. Streaks require an MMOLove account; loyalty accrues for every voter. Omitted only when it can't be resolved at delivery time — a 0 is a real "no loyalty yet" score and is sent.

The reward recipient is username — the in-game name the player entered when voting. Resolve it to an account on your side. Don't reward server_id or heart_id; those identify the vote, not the player.

See the Payload reference for the full contract, including the heart.test shape and how to use heart_id for idempotency.

Verifying signatures

Every callback is signed with an HMAC-SHA256 keyed by your per-server secret (rotate it any time from the Integration tab). Verify the signature before granting a reward so you only act on requests that genuinely came from MMOLove.

The signature is in the X-MMOLove-Signature header:

X-MMOLove-Signature: t=<unix>,v1=<hex>
  • t — Unix seconds at delivery time (equal to the body's timestamp).
  • v1HMAC_SHA256(secret, "<t>.<rawBody>") in lower-case hex, where rawBody is the exact request-body bytes. There is no sha256= prefix on the reward-callback v1 (this differs from the Referral Kit — see the note below).

To verify, recompute the MAC over "<t>.<rawBody>" and compare it to v1 with a constant-time comparison:

expected = HMAC_SHA256(secret, t + "." + rawBody)   // lower-case hex
trust the request  ⇔  expected == v1 (timing-safe)

Read the raw body first. Compute the MAC over the exact bytes you received, before parsing JSON. Re-serializing the parsed object changes whitespace / key order and the MAC will (correctly) fail to match — the single most common integration bug.

Relationship to the Referral Kit signature

Both webhooks use the same HMAC core: HMAC_SHA256(secret, "<t>.<rawBody>"), lower-case hex, signed over the raw body, with t in the X-MMOLove-Signature header. The only difference is the v1 encoding:

Reward callback (this page)Referral Kit
Headert=<unix>,v1=<hex>t=<unix>,v1=sha256=<hex>[,kid=…]
v1 valuebare lower-case hexsha256= + lower-case hex
MACHMAC_SHA256(secret, "<t>.<rawBody>")identical
DirectionMMOLove → you (you verify)you → MMOLove (we verify)
Replay windowenforced on your side (recommended)±5 min, enforced by MMOLove

So if you've already integrated the Referral Kit, your signing core is reusable — just strip the sha256= prefix when verifying a reward callback's v1.

See Signing & verification for the full scheme, a worked example, and a replay-window recommendation.

On this page