Operator configuration
These settings live in Settings → Integration. The two endpoints you must implement — the wallet RPC and the webhook receiver — are specified here.
Enabled games
In Settings → Integration you choose which games your players can launch.
The available games are mystery_box and upgrader. A launch for a game that
isn’t enabled fails with GAME_DISABLED.
The Upgrader’s own knobs — its win-chance presets, house edge, wager limits, and which wager modes (balance / owned items) are allowed — live separately under Settings → Upgrader, not in this enable list. See the Upgrader API.
Currencies & locales
Declare the currencies and locales your brand supports so launches and on-the-fly switching can validate against them. See Localization & currency for the full model.
Custom box attributes
Attach your own typed properties to boxes — a flag, a number, or a short label — without waiting on a code change for each one. Under Boxes → Settings you declare an attribute once, giving it a name, a type (Yes/No, Number, or Text), and a default; then you set its value per box on the box’s edit page. Declaring or changing an attribute is cosmetic — it never republishes the box or changes its odds.
Your declared attributes ride along on every box you read over the API, under
customAttributes, in both the catalog and
box detail. Only attributes you’ve declared are sent, and
the values are typed — a Yes/No attribute is a JSON boolean, a Number is a JSON
number, Text is a string:
"customAttributes": { "is_purchasable": true, "featured_rank": 3 }Because they’re stored as box metadata, your declared attributes are also
filterable and sortable in the catalog via meta.<name> (e.g.
sort=-meta.featured_rank).
Wallet RPC
The synchronous endpoint LootBox Solutions calls for every money movement. You
implement this. Set its HTTPS URL in Settings → Integration; leaving it
empty disables spending (every open returns WALLET_UNAVAILABLE).
All calls are POST, JSON, HMAC-signed with your
outbound secret, and carry a
type field. Money operations also send an Idempotency-Key header equal to
the body’s idempotencyKey.
wallet.debit / wallet.credit
Sent when the player spends (open / shipping cost) or is paid (cashback / refund).
{ "type": "wallet.debit", "playerExternalId": "u_8431", "amountMinor": 500, "currency": "EUR", "idempotencyKey": "round:r_01J…", "reference": { "kind": "round", "publicId": "r_01J…" }}Respond:
// accepted{ "accepted": true, "balanceAfterMinor": 11500, "providerTxnRef": "txn_xyz" }
// refused{ "accepted": false, "errorCode": "INSUFFICIENT_FUNDS" }-
balanceAfterMinorandproviderTxnRefare optional but recommended —balanceAfterMinorlets the game app update the balance without a second call. -
The
referenceobject describes the event for your ledger/reconciliation. Itskindisround,cashback,refundorshipping; acashbackcredit also carries the won item’sitemIdanditemName, so you can label the payout on your side without a second lookup. -
Recognised refusal codes map to player-facing semantics:
your errorCodeplayer sees INSUFFICIENT_FUNDS422 — not enough balance PLAYER_BLOCKED403 — your casino blocked this player anything else / no body 503 — wallet unavailable
wallet.balance
A read for header display and heartbeat refresh.
{ "type": "wallet.balance", "playerExternalId": "u_8431", "currency": "EUR" }Respond { "balanceMinor": 12500, "currency": "EUR" } (or { "errorCode": "…" }).
Rules you must honour
- Timeout: LootBox Solutions waits 10 seconds. Transport failure, non-2xx, or
unparseable JSON all map to
WALLET_UNAVAILABLE; the player can retry, and nothing is lost. - Idempotency: the
idempotencyKeyis stable per real-world event — retries reuse it. Treat a duplicate as “already processed” and return the cached result. Never debit twice for the same key. Key shapes:round:<roundPublicId>— opening a round.cashback:inventory:<inventoryItemId>— cashing back an item.refund:round:<roundPublicId>— refunding a held box.shipping:player:<hash>— a shipping batch.
- Currency: every call names the exact currency being moved. Process the amount in that currency. LootBox Solutions converts the box price once, at purchase, via the rate pinned for the player’s session — the amount and currency are final, and your wallet never does a second conversion. When a player changes display currency mid-session, the next purchase’s calls carry the new currency.
Webhooks
The async endpoint LootBox Solutions POSTs domain events to (set in Settings → Integration), HMAC-signed with your outbound secret. Same envelope; at-least-once delivery with exponential backoff.
A 2xx (any body) marks the dispatch delivered. A 4xx/5xx retries until
the budget is exhausted, then parks the row as failed for reconciliation in
the admin Integration screen. Dedupe on idempotencyKey.
Event types
Two kinds travel the same channel. Subscribe to the ones you need.
Instructions — you must act on these:
type | When | You should |
|---|---|---|
bonus_entitlement | A bonus prize was won | Grant the bonus in your system |
fulfillment_notice | A physical item shipped / changed state | Update your records / notify the player |
Observational — mirror state, no in-band settlement:
type | When |
|---|---|
round_settled | A round resolved (loyalty/XP, analytics) |
box_version_published | A box’s new version went live |
box_updated | Box metadata changed |
box_archived | A box was archived |
kyc_status_changed | A KYC verification request changed status |
Example: bonus_entitlement
{ "type": "bonus_entitlement", "playerExternalId": "u_8431", "idempotencyKey": "bonus:reel:917", "payload": { "itemId": 12, "name": "Free spin", "valueMinor": 0, "currency": "EUR", "roundPublicId": "r_01J…", "metadata": { } }}See Concepts for how bonus vs. cashback are split (cashback is a synchronous wallet credit, not a webhook).
Example: round_settled
Observational — a round resolved. Mirror it for loyalty/XP or analytics; there
is nothing to settle in-band (the money already moved through the wallet RPC).
outcome is the game-specific result blob (reels, won items, …).
{ "type": "round_settled", "playerExternalId": "u_8431", "idempotencyKey": "round_settled:r_01J…", "payload": { "dataVersion": 1, "occurredAt": "2026-06-03T12:00:00Z", "roundPublicId": "r_01J…", "gameKey": "mystery_box", "betMinor": 4200, "payoutMinor": 1500, "currency": "EUR", "playerBetMinor": 4560, "playerCurrency": "USD", "funding": "player", "boxId": 42, "outcome": { } }}betMinor / currency is the canonical operator leg — the round denominated in
your own currency, the value to use for reporting and reconciliation.
playerBetMinor / playerCurrency is the same wager in the currency the player
actually paid, captured at the FX rate pinned for their session. Use it to show
the wager in the player’s own currency. Both fields are null when the round had
no currency conversion (the player and operator currency were the same) — fall
back to betMinor / currency, which then already equals the player’s currency.
funding is player or operator — it tells you whether the player paid for
this open or you funded it (operator-funded),
so you can reconcile a free box differently from a paid round. boxId is the box
this round opened, letting you attribute the settle back to the grant that funded
it.
Example: kyc_status_changed
Observational — a KYC verification request moved through its
lifecycle. One event fires per status change, including creation (the
first event for a request is its PENDING). Signed with the same
HMAC envelope as every outbound call.
{ "type": "kyc_status_changed", "playerExternalId": "u_8431", "idempotencyKey": "kyc:812:SUBMITTED", "payload": { "dataVersion": 1, "requestId": 812, "playerExternalId": "u_8431", "status": "SUBMITTED", "occurredAt": "2026-07-07T12:00:00Z", "submittedAt": "2026-07-07T12:00:00Z", "decidedAt": null }}| Field | Type | Notes |
|---|---|---|
dataVersion | int | Payload schema version, currently 1. |
requestId | int | The verification request. A redo opens a new request — later events carry the new id. Ids only grow, so it doubles as a sort key. |
playerExternalId | string | Your player id, mirrored from the mint. |
status | string | PENDING, SUBMITTED, CONFIRMED, or REJECTED. |
occurredAt | string | When the change happened (ISO 8601 UTC). |
submittedAt | string | null | When the player finished uploading; null until then. |
decidedAt | string | null | When the review decision landed; null until then. |
- Idempotency: the key is
kyc:{requestId}:{status}. A request only moves forward, and a redo gets a freshrequestId, so a repeated key is always a redelivery of the same event — dedupe on it like every other dispatch. - Subscription: delivery is per endpoint — add
kyc_status_changedto your webhook endpoint’s subscribed events in Settings → Integration, or the event is never sent to it. - No PII, ever: the payload carries statuses and timestamps only. The player’s email and their identity documents never appear in a webhook — see data handling.