Wallet contract
The one thing you implement. We call it while a game plays, for every provider we carry, so you integrate one contract instead of one per provider.
Money moves in one direction of calls: the game provider reports to Callisto Games, and Callisto Games calls your wallet. Your wallet never calls us and never talks to the provider. See integration flow →
Request
what we send
POST {your wallet_url}
Content-Type: application/json
X-Agg-Key-Id: ak_5f2c9e01a34b7d8e2f0c1b6a
X-Agg-Timestamp: 1759150800
X-Agg-Signature: hex_lowercase(HMAC_SHA256(secret, timestamp + "." + raw_body))
{
"action": "bet", // getbalance | bet | win | rollback
"transaction_id": "pragmatic:5501934", // the idempotency key; "" on getbalance
"reference_transaction_id": null, // rollback only: the movement being undone
"session_id": "2222aaaa-5b6c-4d7e-8f90-a1b2c3d4e5f6",
"round_id": "7200311", // "" on getbalance
"player_id": "p-1",
"game_id": "vs20fruitsw", // game provider code; null on getbalance
"currency": "EUR",
"amount": "1.50", // string; null on getbalance
"free_spins_id": "promo-7" // only on bets and wins of a free-spin series
}| Field | Meaning |
|---|---|
| transaction_id | Our id for the movement: "<provider>:<provider id>". Treat the whole string as the id. It is the idempotency key. |
| session_id | The session from POST /v1/sessions. Map it back to your player and the catalogue game you launched. |
| game_id | The game provider's code for this round, not the catalogue id. See Game identifiers. |
| amount | A decimal string in the currency's own precision. |
| null fields | Fields without a value are sent as null, except free_spins_id, which is left out. |
Verify the signature
Same formula as your calls to us, with the secret of the key named in X-Agg-Key-Id, over the raw bytes of the body you received. Verify before parsing and refuse anything that does not match with INVALID_SIGNATURE. During a rotation either of your two keys may be named.
Response
| You answer | Body | What we do |
|---|---|---|
| 200 | { "balance": "98.50", "transaction_id": "op-4471" } | Done. balance (required, a string) is after the movement; transaction_id is your own id. |
| 200 on bet or win | { …, "bonus_amount": "0.50" } | Optional: the part of the amount that was bonus money. Reporting it can lower what you are charged; leave it out when there was none. |
| 4xx (not 408, 429) | { "error": "INSUFFICIENT_FUNDS", "message": "…" } | A refusal. Not retried; the provider is told. See Wallet errors. |
| 5xx, 408, 429, no answer | — | Silence. Retried, then rolled back. See Idempotency & retries. |
A 200 must carry a readable balance
A 200 whose body has no string balance (an error object, a number, HTML) is treated as silence, not as success or refusal: it is retried and then rolled back. Refuse with a 4xx.