Skip to content
Wallet integration / Wallet contract
Wallet integration

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.

Wallet callbacks in the integration flow (steps 5 to 7)The operator platform lists games and opens sessions through the Callisto Games Operator API. The player's browser opens the game at launch_url, served by the game provider. The provider reports bets, wins and rollbacks to Callisto Games, which calls the operator wallet at wallet_url and returns the balance. The operator reconciles through /v1/transactions and /v1/rounds.PLAYER'S BROWSEROPERATOR PLATFORMCALLISTO GAMESGAME PROVIDERCasino frontendyour site or appOperator backendyour platformOperator API/v1 · signed X-Agg-*Game clientopened at launch_urlOperator walletyour wallet_urlProvider gatewayone wallet contractGame providergame serverplayer opens a gamelaunch_urlGET /v1/games1POST /v1/sessions2launch_urlGET /v1/transactions8invoice webhookopen launch_url3gameplay: the provider's own traffic, not through the operator4bet · win · rollback5resultPOST wallet_url6balance "98.50"7same session
Wallet callbacks in the integration flow (steps 5 to 7)

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
}
FieldMeaning
transaction_idOur id for the movement: "<provider>:<provider id>". Treat the whole string as the id. It is the idempotency key.
session_idThe session from POST /v1/sessions. Map it back to your player and the catalogue game you launched.
game_idThe game provider's code for this round, not the catalogue id. See Game identifiers.
amountA decimal string in the currency's own precision.
null fieldsFields 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 answerBodyWhat 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.