Skip to content

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 /deposit for 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_id field in the request body.
  • Guarantee from Game Provider on retry: identical request body, identical X-Signature, identical provider_tx_id. No Idempotency-Key HTTP header is sent — dedupe by body field.
  • Logic:
    1. Check if a transaction with this provider_tx_id already exists in your system.
    2. If it exists, return the original response immediately (do not process the balance change again).
    3. If it does not exist, process the transaction and save the result atomically.

[!TIP] This is critical for BET (Withdraw), WIN (Deposit), and ROLL_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-Signature on 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

{
  "user_id": "player123",
  "session_token": "sess-abc-123"
}

Response Payload

{
  "currency": "USD",
  "amount": 10500
}

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_id of 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_ROUND is a notification, not a transaction. amount is always 0, no money moves, and there is no player context on the payload — session_token and user_id are 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:

import json
attrs = {a["name"]: a["value"] for a in payload["attributes"]}
coeffs = json.loads(attrs["aviadroneCashOutCoefficients"])  # -> [2.5, 1.85, ...]
bets   = json.loads(attrs["aviadroneBets"])                 # -> [10000, 5000, ...]
assert len(coeffs) == len(bets)
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>>() {});
var attrs = make(map[string]string)
for _, a := range payload.Attributes {
    attrs[a.Name] = a.Value
}
var coeffs []float64
var bets   []int64
_ = json.Unmarshal([]byte(attrs["aviadroneCashOutCoefficients"]), &coeffs)
_ = json.Unmarshal([]byte(attrs["aviadroneBets"]),                &bets)

Response Payload

A plain 200 acknowledgement is sufficient. The data block is optional — no new_balance is expected because nothing moved.

{
  "code": 200,
  "message": "Success"
}

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 identical provider_tx_id, identical body, identical X-Signature.
  • On a duplicate, return the original 200 response 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 /deposit requests with action: "CLOSE_ROUND" and amount: 0 — do not reject them as "zero deposit".
  • [ ] Allow session_token, user_id, and withdraw_provider_tx_id to be absent.
  • [ ] JSON-parse both aviadroneCashOutCoefficients and aviadroneBets attribute 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 200 even if you have no side-effect to trigger — silence is treated as failure and will be retried.
  • [ ] Never return 402 Insufficient Funds for CLOSE_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_BET actions, 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 and amount is always 0.

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