Events
Every delivery is a POST with a JSON body and Content-Type: application/json; charset=utf-8.
The signature covers those exact bytes, so keep the raw body until you have checked it. See
Verifying signatures.
The envelope
{
"version": 1,
"event": "catalog_item.fulfillment_requested",
"deliveryId": "019f0000-0000-7000-8000-000000000001",
"occurredAtUtc": "2026-08-19T12:00:00+00:00",
"data": {}
}| Field | Type | Meaning |
|---|---|---|
version | integer | The envelope version. Always 1 today. Check it before anything else. |
event | string | The event name, in resource.action form. Also sent as X-Gemifier-Event. |
deliveryId | string (UUID) | What you de-duplicate on. The same on every retry, different between two occasions. Also sent as X-Gemifier-Delivery. |
occurredAtUtc | string (ISO 8601 with offset) | When the event happened, not when the request was made. A retry carries the original value. |
data | object | The payload, different per event. |
Keys inside data are camelCase, and a field with no value is left out rather than sent as
null.
The names you subscribe to in the dashboard are not the names on the wire.
| Subscribe to | Arrives as event | Dashboard label |
|---|---|---|
CatalogItemFulfillmentRequested | catalog_item.fulfillment_requested | Reward to deliver |
MissionCompleted | mission.completed | Mission completed |
StreakMilestoneReached | streak.milestone_reached | Streak milestone |
LeaderboardPeriodClosed | leaderboard.period_closed | Leaderboard period closed |
Your endpoint also sees ping, which you cannot subscribe to. It is on Webhooks.
catalog_item.fulfillment_requested
The one event that asks you to do something. A player earned or bought a catalog item and only your
system can hand it over. deliveryId is the reward id.
{
"version": 1,
"event": "catalog_item.fulfillment_requested",
"deliveryId": "019f0000-0000-7000-8000-000000000001",
"occurredAtUtc": "2026-08-19T12:00:00+00:00",
"data": {
"playerExternalId": "player-42",
"catalogItemKey": "free_spins",
"valueType": "Number",
"value": 20,
"sourceType": "Mission",
"sourceId": "019f0000-0000-7000-8000-000000000002"
}
}| Field | Type | Notes |
|---|---|---|
playerExternalId | string | Your own ID for the player, the one you sent. Never a Gemifier ID. |
catalogItemKey | string | The catalog item's key, for example free_spins. |
valueType | string | One of Number, String, Boolean. |
value | number, string or boolean | What to hand over. Left out when the item has no value, not null. |
sourceType | string | What earned it. See below. |
sourceId | string (UUID) | What that source points at, per sourceType. |
sourceType | sourceId is |
|---|---|
Mission | the completion, the same value mission.completed sends as occurrenceId |
StreakMilestone | the crossing, the same value streak.milestone_reached sends as crossingId |
MarketplacePurchase | the purchase, bought with points rather than earned |
Leaderboard | the leaderboard, not the period that paid it |
Accept a sourceType you do not recognise rather than refusing the delivery.
A 2xx here means you have the instruction and will carry it out, once, even if it arrives again.
It does not mean the player has the item, and Gemifier cannot find that out. After your 2xx
nothing sends it again unless somebody does so by hand.
mission.completed
One player finished one occurrence of one mission. deliveryId is the occurrence id, which is also
data.occurrenceId.
{
"version": 1,
"event": "mission.completed",
"deliveryId": "019f0000-0000-7000-8000-000000000002",
"occurredAtUtc": "2026-08-19T12:00:00+00:00",
"data": {
"playerExternalId": "player-42",
"missionKey": "daily_deposit",
"occurrenceId": "019f0000-0000-7000-8000-000000000002",
"occurrenceNumber": 7,
"completedAtUtc": "2026-08-19T12:00:00+00:00"
}
}| Field | Type | Notes |
|---|---|---|
playerExternalId | string | Your own ID for the player. |
missionKey | string | The mission's key. |
occurrenceId | string (UUID) | This completion, not the mission. A recurring mission's next one is a different id. |
occurrenceNumber | integer | Counts from 0. See below. |
completedAtUtc | string (ISO 8601 with offset) | When the last objective was met. |
occurrenceNumber starts at 0, so the 7 above is the eighth. A mission that does not repeat
always sends 0. A receiver that reads 0 as "missing" drops every one-time completion it is
ever sent. Add one before showing it to a person.
None of these five fields is an amount. To find out what the completion paid, match occurrenceId
against sourceId on the fulfilments it caused. One completion paying three catalog items produces
three of them sharing the id, and one paying only points produces none.
streak.milestone_reached
One player's run crossed one threshold on one day. deliveryId is the crossing id, which is also
data.crossingId.
{
"version": 1,
"event": "streak.milestone_reached",
"deliveryId": "019f0000-0000-7000-8000-000000000003",
"occurredAtUtc": "2026-08-19T12:00:00+00:00",
"data": {
"playerExternalId": "player-42",
"streakKey": "daily_login",
"crossingId": "019f0000-0000-7000-8000-000000000003",
"threshold": 7,
"runLength": 9,
"crossedOn": "2026-08-19",
"reachedAtUtc": "2026-08-19T12:00:00+00:00"
}
}| Field | Type | Notes |
|---|---|---|
playerExternalId | string | Your own ID for the player. |
streakKey | string | The streak's key. |
crossingId | string (UUID) | Matches sourceId on the fulfilments this crossing caused. |
threshold | integer | The milestone reached. |
runLength | integer | How long the run was when it crossed. Can be more than threshold. |
crossedOn | string, yyyy-MM-dd | The player's own day, not a UTC day and not a timestamp. |
reachedAtUtc | string (ISO 8601 with offset) | When the crossing was recorded. |
One day can carry a run past several milestones. Each is its own event with its own crossingId and
the same crossedOn. Reaching the same threshold again after a break is a different crossing, so do
not de-duplicate it against the first.
leaderboard.period_closed
A leaderboard's period settled. Ranks are final and prizes are paid. deliveryId is the period id,
which is also data.periodId.
It is about the race rather than about a player, so it names none. Read the standings from the API.
{
"version": 1,
"event": "leaderboard.period_closed",
"deliveryId": "019f0000-0000-7000-8000-000000000004",
"occurredAtUtc": "2026-09-01T00:00:00+00:00",
"data": {
"leaderboardKey": "weekly_points",
"periodId": "019f0000-0000-7000-8000-000000000004",
"periodStartUtc": "2026-08-25T00:00:00+00:00",
"periodEndUtc": "2026-09-01T00:00:00+00:00",
"participants": 412,
"cohorts": 3,
"settledAtUtc": "2026-09-01T00:00:00+00:00"
}
}| Field | Type | Notes |
|---|---|---|
leaderboardKey | string | The leaderboard's key. |
periodId | string (UUID) | The period that closed. The next one is a different id. |
periodStartUtc | string (ISO 8601 with offset) | When the period opened. |
periodEndUtc | string (ISO 8601 with offset) | When it closed. |
participants | integer | Players who scored in it, across every group. |
cohorts | integer | Groups settled. 1 when everybody races together. |
settledAtUtc | string (ISO 8601 with offset) | When the ranks became final. |
Prizes arrive as their own fulfilments, and their sourceId is the leaderboard rather than this
period.