Wallet API Specification
This section defines the RESTful API that the Operator must implement to handle wallet transactions from Game Provider.
Overview
Game Provider follows a standard server-to-server integration model. The Game Provider platform sends HTTP POST requests to your (the Operator's) backend to authorize players, place bets, settle wins, and (for server-driven games) roll back bets.
- Protocol: HTTP/1.1 or HTTP/2
- Format: JSON
- Requests: Initiated by Game Provider -> Operator
[!NOTE] One contract for all games. Whether the game is a classic iframe title (e.g. Chicken Race) or a server-driven title (e.g. Aviadrone), the Operator implements the same four endpoints listed below. Server-driven titles reuse
/depositfor additional actions (ROLL_BACK,CLOSE_ROUND) — no extra endpoint is required.Endpoints the Operator must expose: 1.
POST {WALLET_BASE_URL}/auth— session + user info 2.POST {WALLET_BASE_URL}/withdraw— bets (including free bets) 3.POST {WALLET_BASE_URL}/deposit— wins, rollbacks, and round-close notifications 4.POST {WALLET_BASE_URL}/balance— current balance
Data Types & Currency
All monetary values in this API are integers represented in millis (1/1000th of the main currency unit).
- Formula:
Amount in Millis = Amount in Unit * 1000 - Example:
5.44 USD->5440(millis) - Example:
1.00 EUR->1000(millis)
[!CAUTION] Do not use floating point numbers. Always send integers.
Idempotency (Required)
Network failures can occasionally cause Game Provider to send the same request multiple times (e.g., a timeout occurs before we receive your response). Game Provider also retries transiently-failed operator calls automatically (see Retry Behavior).
To prevent double-charging or double-crediting players, your Wallet API MUST implement idempotency on withdraw, deposit, and (for server-driven games) rollback deliveries.
- Idempotency key: the
provider_tx_idfield in the request body. - Guarantee from Game Provider on retry: identical request body, identical
X-Signature, identicalprovider_tx_id. NoIdempotency-KeyHTTP header is sent — dedupe by body field. - Logic:
- Check if a transaction with this
provider_tx_idalready exists in your system. - If it exists, return the original response immediately (do not process the balance change again).
- If it does not exist, process the transaction and save the result atomically.
- Check if a transaction with this
[!TIP] This is critical for
BET(Withdraw),WIN(Deposit), andROLL_BACK(Deposit) deliveries to keep player balances consistent under network instability and retry storms.
Security
Every request from Game Provider includes the following headers for security verification:
| Header | Description |
|---|---|
X-Public-Key |
Your assigned public key. Use this to identify the tenant. |
X-Signature |
HMAC SHA256 signature of the request body using your Secret Key. |
[!IMPORTANT] You MUST verify the
X-Signatureon every request. Compute the HMAC SHA256 of the raw request body using your Secret Key and compare it with the header value.View Code Samples for Signature Validation (Java, C#, Go, Python)
Attributes & Metadata
In addition to the standard financial fields, Game Provider may send additional context in the attributes array. This data is useful for auditing, reporting, and responsible gaming checks on the Operator side.
| Attribute | Type | Sent on | Description |
|---|---|---|---|
coefficient |
String | /deposit (WIN) |
The winning multiplier for the round (e.g., "1.50"). |
bonus |
String | /withdraw (FREE_BET), /deposit (FREE_BET_WIN) |
Flag or amount related to bonus balance usage or promotional eligibility. |
createDate |
String | any | ISO 8601 timestamp of when the event occurred in the game engine. |
aviadroneCashOutCoefficients |
String (JSON array) | /deposit (CLOSE_ROUND, Aviadrone) |
JSON-encoded array of per-bet cashout multipliers for the closed round. See Close Round. |
aviadroneBets |
String (JSON array) | /deposit (CLOSE_ROUND, Aviadrone) |
JSON-encoded array of per-bet amounts in millis for the closed round. See Close Round. |
Each entry in the array is an object with name and value properties:
"attributes": [
{"name": "coefficient", "value": "1.50"},
{"name": "bonus", "value": "freebet"},
{"name": "createDate", "value": "2024-12-24T14:30:00Z"}
]
Endpoints
1. Authentication Check
Verifies the player's session and retrieves their balance.
- URL:
{WALLET_BASE_URL}/auth - Method:
POST
Request Payload
{
"user_token": "player123",
"session_token": "sess-abc-123",
"platform": "mobile",
"currency": "USD"
}
Response Payload
{
"code": 200,
"message": "OK",
"data": {
"user_id": "player123",
"username": "Player One",
"balance": 10000000,
"currency": "USD",
"maxbet": 5000000,
"minbet": 100,
"maxwin": 100000000
}
}
| Field | Type | Required | Description |
|---|---|---|---|
data.user_id |
String | Yes | Stable, unique player ID on your side. |
data.username |
String | Yes | Display name of the player. |
data.balance |
Integer (millis) | Yes | Current real balance. |
data.currency |
String | Yes | 3-letter ISO currency (must match session currency). |
data.maxbet |
Integer (millis) | Yes | Per-round bet cap for the player. |
data.minbet |
Integer (millis) | No | Per-round minimum bet. Optional. |
data.maxwin |
Integer (millis) | No | Per-round winnings cap. Optional. |
2. Withdraw (Bet)
Deducts funds from the player's wallet.
- URL:
{WALLET_BASE_URL}/withdraw - Method:
POST
Request Payload
{
"currency": "USD",
"amount": 5440,
"provider": "Game Provider",
"provider_tx_id": "tx-1001",
"game": "chicken-race",
"action": "BET",
"action_id": "round-555",
"session_token": "sess-abc-123",
"platform": "mobile",
"user_id": "player123",
"attributes": [
{"name": "createDate", "value": "2024-12-24T14:30:00Z"}
]
}
Response Payload
{
"code": 200,
"message": "Success",
"data": {
"user_id": "player123",
"operator_tx_id": "op-tx-999",
"provider_tx_id": "tx-1001",
"new_balance": 10000000,
"currency": "USD"
}
}
3. Deposit (Win)
Adds funds to the player's wallet.
- URL:
{WALLET_BASE_URL}/deposit - Method:
POST
Request Payload
{
"currency": "USD",
"amount": 1000,
"provider": "Game Provider",
"provider_tx_id": "tx-1002",
"withdraw_provider_tx_id": "tx-1001",
"game": "chicken-race",
"action": "WIN",
"action_id": "round-555",
"session_token": "sess-abc-123",
"platform": "mobile",
"user_id": "player123",
"attributes": [
{"name": "coefficient", "value": "1.50"},
{"name": "createDate", "value": "2024-12-24T14:35:00Z"}
]
}
Response Payload
{
"code": 200,
"message": "Success",
"data": {
"user_id": "player123",
"operator_tx_id": "op-tx-1000",
"provider_tx_id": "tx-1002",
"new_balance": 10500,
"currency": "USD"
}
}
4. Get Balance
Retrieves the current balance of the player.
- URL:
{WALLET_BASE_URL}/balance - Method:
POST
Request Payload
Response Payload
5. Rollback (Server-Driven Games Only)
For server-driven titles (e.g. Aviadrone), Game Provider may roll back a previously accepted bet — for example, when a round is voided. Rollbacks are delivered on the existing /deposit endpoint; no separate rollback endpoint is required.
A rollback is a POST /deposit with:
action="ROLL_BACK"amount= the original bet's amount (you do not need to recompute it)withdraw_provider_tx_id=provider_tx_idof the bet being reversed- Its own unique
provider_tx_id(treat like any new transaction for idempotency)
Example
{
"currency": "USD",
"amount": 5440,
"provider": "Game Provider",
"provider_tx_id": "tx-1003",
"withdraw_provider_tx_id": "tx-1001",
"game": "aviadrone",
"action": "ROLL_BACK",
"action_id": "round-555",
"session_token": "sess-abc-123",
"platform": "desktop",
"user_id": "player123",
"attributes": []
}
On success, credit the player back the full original bet amount and return the same TransferResponseDTO envelope used for WIN. If you have already refunded this provider_tx_id, return the original response (idempotent).
6. Close Round (Server-Driven Games Only)
For server-driven titles (currently Aviadrone), Game Provider delivers a single CLOSE_ROUND event per round so the Operator can run post-round side-effects that have nothing to do with money — loyalty triggers, promotional unlocks, tournaments, BI aggregation, accounting close-of-book, responsible-gaming limits, etc.
- URL:
{WALLET_BASE_URL}/deposit - Method:
POST - Action:
CLOSE_ROUND
[!NOTE]
CLOSE_ROUNDis a notification, not a transaction.amountis always0, no money moves, and there is no player context on the payload —session_tokenanduser_idare intentionally omitted. Game Provider authenticates the call with HMAC-SHA256 (X-Public-Key+X-Signature) exactly like every other callback; the Operator is identified by the public key, not by a player session.
When it is sent
| Property | Value |
|---|---|
| Frequency | One CLOSE_ROUND per (round_id, operator) pair. If multiple Operators had bets in the same round, each Operator receives their own CLOSE_ROUND with their own players' data. |
| Ordering | Always sent after every BET, WIN, and ROLL_BACK for that round has been delivered and 200-acknowledged. |
| Trigger | Round reaches its terminal state (crash, cashout, or voided). |
| Empty rounds | If an Operator had no bets in a round, no CLOSE_ROUND is sent for that Operator. |
Request Payload
{
"amount": 0,
"provider": "Game Provider",
"provider_tx_id": "tx-cr-9001",
"game": "aviadrone",
"action": "CLOSE_ROUND",
"action_id": "round-555",
"attributes": [
{ "name": "aviadroneCashOutCoefficients", "value": "[2.50, 1.85, 1.00, 3.20, 1.00]" },
{ "name": "aviadroneBets", "value": "[10000, 5000, 2500, 15000, 1000]" }
]
}
| Field | Type | Required | Description |
|---|---|---|---|
amount |
Integer | Yes | Always 0. CLOSE_ROUND never moves money. |
provider |
String | Yes | Fixed string identifying the Game Provider. |
provider_tx_id |
String | Yes | Unique idempotency key for this notification — treat like any other /deposit. |
game |
String | Yes | Game identifier (e.g. "aviadrone"). |
action |
String | Yes | Literal "CLOSE_ROUND". |
action_id |
String | Yes | The round_id that is being closed. |
attributes |
Array | Yes | Exactly two entries, described below. |
session_token |
— | Omitted | Intentionally not sent. CLOSE_ROUND is round-level, not player-level. |
user_id |
— | Omitted | Intentionally not sent. Aggregated payload covers all of your operator's players in the round. |
currency |
— | Omitted | Can be absent. Derive per-bet currency from your internal (operator, round_id) → bet records if needed. |
withdraw_provider_tx_id |
— | Omitted | CLOSE_ROUND is not linked to any single bet. |
Attribute Payload
Both attributes carry JSON-encoded arrays as strings. The two arrays are positionally aligned: index i in aviadroneCashOutCoefficients refers to the same bet as index i in aviadroneBets.
| Attribute | JSON shape | Item meaning |
|---|---|---|
aviadroneCashOutCoefficients |
JSON array of decimal numbers (as strings inside the JSON string) | Per-bet payout multiplier. 0 (or 1.00 or lower) for a lost bet, otherwise the cashout multiplier (e.g. 2.50). |
aviadroneBets |
JSON array of integers | Per-bet stake in millis (same convention as every other field on this API). |
Parsing tip
The attribute value is a string on the wire. You must JSON-parse it in your handler:
Map<String, String> attrs = payload.getAttributes().stream()
.collect(Collectors.toMap(Attribute::getName, Attribute::getValue));
List<BigDecimal> coeffs = mapper.readValue(
attrs.get("aviadroneCashOutCoefficients"),
new TypeReference<List<BigDecimal>>() {});
List<Long> bets = mapper.readValue(
attrs.get("aviadroneBets"),
new TypeReference<List<Long>>() {});
Response Payload
A plain 200 acknowledgement is sufficient. The data block is optional — no new_balance is expected because nothing moved.
The following richer response is also accepted and matches the envelope used for WIN and ROLL_BACK:
{
"code": 200,
"message": "Success",
"data": {
"operator_tx_id": "op-cr-42",
"provider_tx_id": "tx-cr-9001"
}
}
Idempotency
CLOSE_ROUND uses the same idempotency contract as every other /deposit:
- Deduplicate by
provider_tx_id. - Game Provider's deterministic key format is
{round_id}-{operator_id}-close-round, so a retry of the same round notification will arrive with an identicalprovider_tx_id, identical body, identicalX-Signature. - On a duplicate, return the original
200response without re-running any side-effects (loyalty grants, BI rows, etc.).
[!IMPORTANT] If your post-round hooks (promotions, tournament point awards, responsible-gaming counters) are non-idempotent by themselves, gate them on a first-time-seen check against
provider_tx_id. A network retry must not double-award points.
Operator Implementation Checklist
- [ ] Accept
/depositrequests withaction: "CLOSE_ROUND"andamount: 0— do not reject them as "zero deposit". - [ ] Allow
session_token,user_id, andwithdraw_provider_tx_idto be absent. - [ ] JSON-parse both
aviadroneCashOutCoefficientsandaviadroneBetsattribute values. - [ ] Treat the two arrays as positionally aligned; reject payloads where lengths differ (
400). - [ ] Dedupe by
provider_tx_id(same as any other/deposit). - [ ] Respond
200even if you have no side-effect to trigger — silence is treated as failure and will be retried. - [ ] Never return
402 Insufficient FundsforCLOSE_ROUND; no debit is taking place.
Example: lost-only round
A round where every bet from your operator lost (no cashouts) — payload shape is identical, with 0 in every coefficient slot:
{
"amount": 0,
"provider": "Game Provider",
"provider_tx_id": "tx-cr-9002",
"game": "aviadrone",
"action": "CLOSE_ROUND",
"action_id": "round-556",
"attributes": [
{ "name": "aviadroneCashOutCoefficients", "value": "[0, 0, 0]" },
{ "name": "aviadroneBets", "value": "[10000, 5000, 20000]" }
]
}
7. Action Types
The action field in Withdraw and Deposit requests indicates the nature of the transaction.
| Action | Endpoint | Games | Moves money? | Description |
|---|---|---|---|---|
BET |
/withdraw |
classic + server-driven | Yes (debit) | Standard real-money bet. |
WIN |
/deposit |
classic + server-driven | Yes (credit) | Standard real-money win. |
FREE_BET |
/withdraw |
classic + server-driven | No (amount = 0) |
Bet using a gift/bonus. |
FREE_BET_WIN |
/deposit |
classic + server-driven | Yes (credit) | Win resulting from a free bet. |
ROLL_BACK |
/deposit |
server-driven only | Yes (credit) | Refund of a previously accepted bet — see section 5. |
CLOSE_ROUND |
/deposit |
server-driven only (Aviadrone) | No (amount = 0) |
Round-level notification, emitted once per (round, operator) — see section 6. |
For
FREE_BETactions, verify the player is eligible but do not deduct funds from their main balance.For
CLOSE_ROUND, do not touch the player's balance at all — the payload has no player andamountis always0.
Example: Free Bet Request
When a player uses a free bet, the action is set to FREE_BET, the amount is 0, and the bonus attribute is included.
{
"currency": "USD",
"amount": 0,
"provider": "Game Provider",
"provider_tx_id": "tx-2001",
"game": "chicken-race",
"action": "FREE_BET",
"action_id": "round-999",
"session_token": "sess-xyz-789",
"platform": "desktop",
"user_id": "player123",
"attributes": [
{"name": "bonus", "value": "freebet"},
{"name": "createDate", "value": "2024-12-24T15:00:00Z"}
]
}
Retry Behavior
Game Provider retries transient operator failures (HTTP 5xx, timeouts, connection errors) automatically. Each retry resends the exact same body, the same X-Signature, and the same provider_tx_id — so a correctly-implemented idempotent handler will naturally return the same response.
| Parameter | Value |
|---|---|
| Base backoff | 5 seconds |
| Max backoff | 2 minutes |
| Jitter | up to 300 ms |
| Max attempts | 5 |
4xx responses are treated as terminal — Game Provider will not retry a 400/401/402/404/409.
Error Codes
For the full catalog of Game Provider error codes and their HTTP status mappings, see Error Handling.
The Operator's own responses to Game Provider should use standard HTTP status codes:
| Code | Meaning on Operator Response |
|---|---|
200 |
Success |
400 |
Bad request / validation error (will not be retried) |
401 |
Invalid signature or unknown public key (will not be retried) |
402 |
Insufficient funds (will not be retried) |
404 |
User / session not found (will not be retried) |
409 |
Duplicate provider_tx_id detected but request differs — return original response instead when possible |
5xx |
Transient failure — Game Provider will retry |