MMOLove Docs
Reward Callbacks — guides

Payload reference

The exact heart.counted body and headers MMOLove delivers — every field, the heart.test shape, and idempotency via heart_id.

This is the authoritative contract for the reward-callback request. When a heart is counted, MMOLove POSTs this to your callback URL.

The request

POST /your/callback/path
Content-Type: application/json
X-MMOLove-Event: heart.counted
X-MMOLove-Signature: t=<unix>,v1=<hex>
HeaderValue
Content-Typeapplication/json
X-MMOLove-EventThe event name — heart.counted for a real vote, heart.test for a dashboard test callback. Lets you branch without parsing the body.
X-MMOLove-Signaturet=<unix>,v1=<hex> — verify this before trusting the body. See Signing.

The body (heart.counted)

{
  "event": "heart.counted",
  "server_id": "8f0e1d2c-3b4a-5c6d-7e8f-901a2b3c4d5e",
  "username": "PlayerOne",
  "heart_id": "1a2b3c4d-5e6f-7081-92a3-b4c5d6e7f809",
  "period": "2026-06",
  "timestamp": 1733500000,
  "streak_day": 7
}
FieldTypeAlways presentMeaning
eventstringyesheart.counted (real vote) or heart.test (dashboard test). Mirrors the X-MMOLove-Event header.
server_idstringyesYour MMOLove server id (UUID). The server the heart was cast for.
usernamestringyesThe voter's in-game name — the reward recipient. Exactly what the player entered when voting.
heart_idstringyesThe vote's unique id (UUID). Use as your idempotency key.
periodstringyesThe vote's ranking period, YYYY-MM in UTC, e.g. 2026-06.
timestampnumberyesUnix seconds at delivery time. Equals the t in the signature.
streak_daynumbernoThe voter's current daily vote streak (days) for this server at delivery time. Field is omitted when the streak is unavailable.

The reward recipient is username — resolve it to an account on your side. Don't reward server_id (that's your server) or heart_id (that's the vote).

streak_day — escalating rewards

streak_day is the voter's consecutive-daily-vote count for your server at the moment of delivery, so you can scale rewards: a flat reward at day 1, a bonus at day 7, a big bonus at day 30, and so on.

It is optional. When MMOLove can't resolve the voter's streak the field is simply left out of the JSON (it is never sent as 0 to mean "unknown"). So:

streak_day present  → use it (1, 2, 7, …)
streak_day absent   → treat as "no streak info", grant the base reward

Don't default a missing streak_day to 0 and then withhold a base reward — a missing field still represents a real, counted vote.

Idempotency — use heart_id

A delivery is retried until it gets a 2xx (see Errors & delivery), so the same heart_id can legitimately arrive more than once — e.g. you granted the reward but your 2xx response was lost in transit.

Make granting idempotent by keying on heart_id:

if (already_rewarded(heart_id)) return 200;   // safe replay — acknowledge
grant_reward(username, streak_day);
mark_rewarded(heart_id);
return 200;

This way a retried delivery is a guaranteed no-op, and you can always return a clean 2xx so MMOLove stops retrying.

The heart.test shape

The dashboard's Send test callback delivers a heart.test event — identical in shape and signing to a real one, with placeholder values so your handler can run end-to-end without a real vote:

{
  "event": "heart.test",
  "server_id": "<your-server-id>",
  "username": "test-user",
  "heart_id": "00000000-0000-0000-0000-000000000000",
  "period": "2026-06",
  "timestamp": 1733500000
}

Note: heart.test carries the all-zero heart_id and the username test-user, and (because there's no real voter) no streak_day. Branch on event === "heart.test" (or the X-MMOLove-Event header) to verify-and-acknowledge without actually paying out. See Testing & self-verify.

What you return

Your responseMMOLove does
2xxMarks the delivery delivered. Stops.
any non-2xxSchedules a retry with backoff (up to 6 attempts).
timeout / connection errorSame as non-2xx — retry.

Return a 2xx as soon as you've durably accepted the vote (recorded the heart_id), even if the in-game grant is async. Return non-2xx only when you truly want a retry.

See also

On this page