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>| Header | Value |
|---|---|
Content-Type | application/json |
X-MMOLove-Event | The event name — heart.counted for a real vote, heart.test for a dashboard test callback. Lets you branch without parsing the body. |
X-MMOLove-Signature | t=<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,
"account_linked": true,
"streak_day": 7,
"longest_streak": 12,
"streak_milestone": true,
"monthly_streak": 3,
"longest_monthly_streak": 5,
"loyalty_score": 240,
"loyalty_level": 3,
"loyalty_title": "Devotee",
"total_hearts": 18,
"first_heart": false
}| Field | Type | Always present | Meaning |
|---|---|---|---|
event | string | yes | heart.counted (real vote) or heart.test (dashboard test). Mirrors the X-MMOLove-Event header. |
server_id | string | yes | Your MMOLove server id (UUID). The server the heart was cast for. |
username | string | yes | The voter's in-game name — the reward recipient. Exactly what the player entered when voting. |
heart_id | string | yes | The vote's unique id (UUID). Use as your idempotency key. |
period | string | yes | The vote's ranking period, YYYY-MM in UTC, e.g. 2026-06. |
timestamp | number | yes | Unix seconds at delivery time. Equals the t in the signature. |
streak_day | number | no | The voter's current daily vote streak (days) for this server at delivery time. Field is omitted when the streak is unavailable. Present only for voters signed in to MMOLove (streaks are account-based). |
loyalty_score | number | no | The 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 score (first heart, no prior loyalty) and is sent. |
account_linked | boolean | yes | Whether the voter is signed in to an MMOLove account. false for anonymous voters, who never carry streak/loyalty. Gate streak rewards on this. |
longest_streak | number | no | Best-ever daily streak for this server. Account-linked voters only. |
streak_milestone | boolean | no | true when this heart hit a 7-day multiple (7, 14, …) — a ready-made milestone trigger. Account-linked voters only. |
monthly_streak | number | no | Consecutive calendar months (ending this month) the voter has voted for this server — for monthly reward cycles. Account-linked voters only. |
longest_monthly_streak | number | no | Best-ever consecutive-months run for this server. Account-linked voters only. |
loyalty_level | number | no | Loyalty level (1–8) for this server, from loyalty_score. Present whenever loyalty_score is. |
loyalty_title | string | no | Level title (Newcomer … Ascended). Present whenever loyalty_level is. |
total_hearts | number | no | Lifetime counted hearts for this server. |
first_heart | boolean | no | true on the voter's first-ever counted heart for this server — for welcome rewards. |
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, and present only for voters signed in to MMOLove — streaks
are account-based, so anonymous votes never carry one. 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 rewardDon't default a missing streak_day to 0 and then withhold a base reward — a
missing field still represents a real, counted vote.
loyalty_score — reward devotion
loyalty_score is the voter's cumulative Loyalty Score with your server —
earned per counted heart (+10) and through streak milestones. Use it to tier
rewards for your most devoted players (e.g. a cosmetic at 100, a mount at 1000).
Streaks require an MMOLove account, but loyalty accrues for every voter.
Its semantics differ subtly from streak_day: a resolved 0 is sent — it's a
real score meaning "first heart, no prior loyalty". The field is omitted only
when MMOLove can't resolve the score at delivery time. So:
loyalty_score present → use it (0, 10, 240, …) — 0 is a real first-heart score
loyalty_score absent → treat as "no loyalty info", grant the base rewardStreaks: daily vs monthly
Two streak granularities cover both habits:
streak_day/longest_streak— consecutive days. Reward not missing a day.monthly_streak/longest_monthly_streak— consecutive calendar months with at least one vote (theperiodbucket). Reward not churning for a month — ideal for seasonal/monthly reward cycles. A vote anywhere in the month keeps it alive.
if (event.monthly_streak >= 3) grantMonthlyLoyalty(event.username, event.monthly_streak);Both are present only for account-linked voters; a missing field means "no streak info".
Milestone & first-vote flags
streak_milestone—trueexactly when the daily streak just hit 7, 14, 21, … days.first_heart—trueon the voter's very first counted heart for your server.
if (event.streak_milestone) grantMilestoneBonus(event.username, event.streak_day);
if (event.first_heart) grantWelcome(event.username);Loyalty level
loyalty_level (1–8) + loyalty_title save you hard-coding XP tiers: gate on
loyalty_level >= 4 ("Champion and above") instead of a raw score. They derive from
loyalty_score, so they appear together.
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,
"loyalty_score": 120
}Note: heart.test carries the all-zero heart_id, the username test-user,
and a placeholder loyalty_score of 120, but (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 response | MMOLove does |
|---|---|
2xx | Marks the delivery delivered. Stops. |
| any non-2xx | Schedules a retry with backoff (up to 6 attempts). |
| timeout / connection error | Same 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
Quickstart
From zero to a verified reward callback in about five minutes — set the URL, copy your secret, send a test callback, watch it land, then verify and reward.
Rewarding streaks & loyalty
Copy-paste recipes for granting in-game rewards from a player's MMOLove daily streak, monthly streak, milestones, loyalty level and first vote.