Bet
Register a player bet
Called when a player places a bet. You must deduct the bet amount from the player's balance.
Endpoint
POST /betRequest
POST /bet HTTP/1.1
Host: your.game.api
X-Marbles-Signature: <signature>
Content-Type: application/json
{
"requestId": "8df0475e-5069-483a-8205-f6089997abc9",
"transactionId": "ea0240f5-483d-434b-a8d4-04dabf61cde3",
"clientSessionId": "0k3cz83bb3h2vn53ocnc7pxw9",
"clientPlayerId": "02mnrpyv2qd9jbwhoniyimxsy",
"roundId": "17cc81fd-df13-4ca4-857d-de0f766dc372",
"gameId": "f1c0b104-f29d-44a9-ae93-e8afcbe3feb9",
"amount": 1000000,
"currency": "USD",
"meta": {
"betType": "PickWinner",
"selection": [
"40e5e0eb-cc5a-4a81-a157-7a4b641c05df",
"17fcb2c6-f0ac-4855-98ef-9e7e3fd738bd",
"bb65a881-a4fe-4b65-aae5-e085a9f96cd3"
]
},
"timestamp": "2025-06-15T14:30:00.609Z"
}Request Fields
| Field | Type | Description |
|---|---|---|
requestId | string | Unique request identifier (UUID) |
transactionId | string | Unique transaction identifier for this bet |
clientSessionId | string | The player's session identifier |
clientPlayerId | string | The player's unique identifier |
roundId | string | Per-bet round reference. Unique per bet — a player placing several chips in one game round produces several /bet calls, each with its own roundId (see Round IDs) |
gameId | string | The game identifier |
amount | int64 | Bet amount, an integer in the currency's wire precision (see Currencies) |
currency | string | ISO 4217 currency code |
isFree | boolean | true if this is a freebet (see below) |
rewardUuid | string | The reward identifier for freebets |
meta | object | Additional bet metadata |
meta.betType | string | Type of bet placed |
meta.selection | array | Selected items for the bet |
timestamp | string | RFC 3339 UTC timestamp of when the request was created. Fractional seconds vary by callback — parse leniently, do not pin an exact format string |
Freebet Requests
When a player uses a freebet, the request includes additional fields:
{
"requestId": "...",
"transactionId": "...",
"amount": 0,
"currency": "USD",
"isFree": true,
"rewardUuid": "promo-winter-2025-001",
"meta": { ... }
}| Field | Description |
|---|---|
amount | Always 0 for freebets (no real money deducted) |
isFree | true to indicate this is a freebet |
rewardUuid | Your clientRewardId from when you granted the freebet |
For freebets, do not deduct any balance from the player. The amount will be 0. See Freebets & Rewards for more details.
Response Contract
Always respond HTTP 200; put the outcome in status. We read the status field, not the HTTP code, to decide what happens to the bet.
status | When you return it | What 155 does |
|---|---|---|
SUCCESS | Bet accepted, stake debited | Bet is live |
INSUFFICIENT_BALANCE_ERROR | Player can't cover the stake | Bet rejected, surfaced to player |
DUPLICATE_TRANSACTION_ERROR | This transactionId was already processed | Treated as the original; no re-debit |
BET_LIMIT_REACHED_ERROR | Operator bet-limit hit | Bet rejected |
BONUS_ERROR | Free-bet/reward problem | Bet rejected |
UNKNOWN_ERROR | Unexpected operator-side failure | Bet rejected — a /rollback for this transactionId follows (you may receive several attempts within a few seconds, each with a different rollback transactionId but the same referenceTransactionId). The /bet is not re-sent |
Never return a bare failed to create bet message or a non-200 HTTP status — always return one of the status values above. ROUND_CLOSED is not a bet status: it's a 155-side condition, not something you return.
The one exception is authentication: if you verify X-Marbles-Signature and it fails, reject the request with a non-2xx (e.g. 401) — that's an auth failure, not a bet outcome. The status values above apply only to requests that passed signature verification. (A bad signature never occurs on genuine 155 traffic, which is always correctly signed, so this can't affect real bets.)
See Error codes for the canonical enum across all callbacks.
Idempotency
Replaying a /bet with the same transactionId must return DUPLICATE_TRANSACTION_ERROR with no balance change — it is a duplicate, not a new bet (network-retry safety). Do not debit the stake a second time.
This is asymmetric with /win: a replayed /win returns the original SUCCESS result, whereas a replayed /bet returns DUPLICATE_TRANSACTION_ERROR.
Aggregator (Hub88) integrations use a different status set — this page is for direct integrations.
Success Response
HTTP/1.1 200 OK
X-Marbles-Signature: <signature>
Content-Type: application/json
{
"status": "SUCCESS",
"requestId": "8df0475e-5069-483a-8205-f6089997abc9",
"clientPlayerId": "02mnrpyv2qd9jbwhoniyimxsy",
"currency": "USD",
"balance": 1000000
}Response Fields
| Field | Type | Description |
|---|---|---|
status | string | "SUCCESS" |
requestId | string | Echo back the request ID |
clientPlayerId | string | Echo back the player ID |
currency | string | ISO 4217 currency code |
balance | int64 | New balance after bet, in the currency's wire precision (see Currencies) |
Error Response
HTTP/1.1 200 OK
X-Marbles-Signature: <signature>
Content-Type: application/json
{
"status": "INSUFFICIENT_BALANCE_ERROR",
"requestId": "8df0475e-5069-483a-8205-f6089997abc9",
"clientPlayerId": "02mnrpyv2qd9jbwhoniyimxsy"
}Error Statuses
| Status | Description |
|---|---|
INSUFFICIENT_BALANCE_ERROR | Player does not have enough balance |
DUPLICATE_TRANSACTION_ERROR | This transactionId was already processed (idempotent replay) |
BONUS_ERROR | Bet does not pass free-bet / reward rules |
BET_LIMIT_REACHED_ERROR | Player has reached their betting limit |
UNKNOWN_ERROR | Bet could not be registered |
For an expired or invalid player session, return UNKNOWN_ERROR. There is no dedicated session-state status on the wallet callbacks — any status outside the enum above is treated the same as UNKNOWN_ERROR (the bet is rejected and the player sees a generic failure).
A failed or timed-out /bet triggers a /rollback
If your /bet returns UNKNOWN_ERROR (or an unrecognised status), responds non-200, or exceeds the 8-second deadline, we treat the bet as rejected and send a /rollback for that transactionId — even if your wallet actually accepted and debited it after the response was lost. Implement /rollback to tolerate both cases (refund if debited; SUCCESS with no movement if unknown). If the transactionId was already processed on your side, return DUPLICATE_TRANSACTION_ERROR — a duplicate is never rolled back.
See the Response Contract above for what 155 does with each status, and Error codes for the canonical enum.