Wallet callbacks¶
The HTTP endpoints your wallet exposes to 19flip, with the exact request bodies we send and the responses we expect. 19flip calls them whenever a game reads a balance or moves money, and (Flip Jet) when a shared game round ends. Your wallet is the only place player money lives.
Overview¶
All callbacks are POST requests with a JSON body to a path under your callback_url. The path is appended with a single /: callback_url https://casino.example.com/api/providers/19flip gives https://casino.example.com/api/providers/19flip/bet/debit.
| Endpoint | Money effect | Idempotency key | Response |
|---|---|---|---|
POST {callback_url}/balance |
none | – | Balance response |
POST {callback_url}/bet/debit |
balance − amount |
yes | Transaction response |
POST {callback_url}/win/credit |
balance + amount |
yes | Transaction response |
POST {callback_url}/bet/rollback |
balance + amount (undo a debit) |
yes | Transaction response |
POST {callback_url}/win/rollback |
balance − amount (undo a credit) |
yes | Transaction response |
POST {callback_url}/round/finish |
none (Flip Jet only) | yes | any 2xx, see /round/finish |
Every request carries Content-Type: application/json, Authorization: Bearer <api_key>, X-API-Key: <api_key> and X-Signature. Verify them as described in Authentication.
Requirements for your endpoints:
- Answer within 10 seconds. See Idempotency & retries for timeouts and retries.
- Answer with a flat JSON object. Do not wrap it in
{"success": ..., "data": ...}: we read the fields from the top level, and a wrapped answer is read as a balance of0. - Answer
2xxonly when the operation was applied (or was already applied under the sameidempotency_key). Signal a refusal with a4xxstatus. See Errors. - Do not redirect. Configure the final URL as
callback_url, without a query string.
Calls from the integration test¶
New in 2.7.0
19flip can run an integration test against your wallet and send you a report.
When 19flip runs the integration test on your account, the same five endpoints receive ordinary signed callbacks with the same headers, one attempt each and a 10-second timeout. You can tell them apart by: idempotency_key starting with itest-, round_id between 900000 and 999999, game_id null, reason "integration test" on rollbacks, and a session_id you did not issue unless you gave us one. One request is deliberately signed with a wrong secret and must get 401.
Common fields¶
| Field | Type | In | Description |
|---|---|---|---|
player_id |
string | all | The player_id you sent in launch-game. |
currency |
string | all | 3-letter currency of the session. |
session_id |
string | all | The session_id returned by launch-game (a UUID). The connection test sends "healthcheck". |
amount |
number | money calls | Always > 0. A JSON number such as 1.5 or 10.0, normally with at most 2 decimal places. |
round_id |
integer | money calls | The game's round number. Unique only within one game. Usually a JSON integer; operations re-sent manually by 19flip staff carry it as a string of digits (e.g. "48213"), so accept both. |
idempotency_key |
string | money calls | Unique per intended money movement. Deduplicate on it. Treat it as an opaque string (current keys are up to 64 characters; store at least 255). |
game_id |
string or null | debit, credit | Game id, e.g. "bingo-bongo". May be null (Block Boost and Mister Twister do not send it). Use session_id to know the game. |
transaction_id |
string or null | debit, credit | The game's internal reference, usually null. Informational only; your response's transaction_id is a different thing. |
original_transaction_id |
string | rollbacks | Which operation to undo. Your transaction_id from the original response when 19flip received one; otherwise the original operation's idempotency_key. |
reason |
string or null | rollbacks | Free text, e.g. "round cancelled", "manual rollback". |
game_round_id |
integer | debit, credit, bet rollback (Flip Jet only) | The shared game round the bet plays in: the same number as game_round_id in /round/finish. In Flip Jet round_id is per bet, so use this field to group a round's bets. Absent for other games. |
multiplier |
string | credit (Flip Jet only) | The multiplier at which the bet was cashed out, two decimals, e.g. "2.35". amount = bet × multiplier, rounded down. Absent for other games. |
round_type |
string | debit, credit, bet rollback (Flip Jet only) | "golden_flight" for a Golden Flight purchase. The debit amount is then the price (base bet × 20, 50 or 100), which may exceed your max_bet. Absent for ordinary bets and for other games. |
The fields are always present in the order shown in the examples below; fields that have no value are sent as null. Exception: game_round_id, multiplier and round_type are appended at the end only by the games that send them and are absent otherwise.
Added in 2.73.0
round_type (Flip Jet Golden Flight).
Added in 2.34.0
game_round_id and multiplier (Flip Jet). Ignore unknown fields in callback bodies: new optional fields are added at the end.
Transaction response¶
Returned by all four money endpoints with HTTP 200:
{
"transaction_id": "5b0e1c52-3f7e-4d7c-9a8e-0f5d2a61c9e4",
"balance_before": 100.0,
"balance_after": 98.5,
"status": "SUCCESS"
}
| Field | Type | Required | Description |
|---|---|---|---|
transaction_id |
string | yes | Your id for this operation. 19flip stores it and sends it back as original_transaction_id when this operation is rolled back. Must be unique in your system. |
balance_before |
number | yes | Player balance before the operation. |
balance_after |
number | yes | Player balance after the operation. Games show it to the player. |
status |
string | yes | SUCCESS. See the note below for other values. |
How 19flip reads your answer:
| Your answer | What 19flip records |
|---|---|
2xx, all four fields, "status": "SUCCESS" |
Operation succeeded. |
2xx, status missing or not one of SUCCESS / FAILED / PENDING |
Recorded as PENDING; it goes to manual reconciliation after 5 minutes. |
2xx, "status": "PENDING" |
Same as above. Avoid it: 19flip has no way to ask you later. |
2xx, "status": "FAILED" |
Recorded as failed, and passed to the game as a normal 2xx answer. Some games treat any 2xx as money moved. Do not use this to decline; answer 4xx instead. |
2xx, not JSON, or a JSON envelope |
Balances read as 0, transaction_id empty, status PENDING. |
4xx |
Definitive refusal; not retried. 402 means insufficient funds. |
5xx, timeout, connection error |
Outcome unknown; retried with the same body and key. |
Balance response¶
Returned by /balance with HTTP 200:
| Field | Type | Required | Description |
|---|---|---|---|
balance |
number | yes | Current balance available for play in the requested currency. If missing, 0 is used. |
currency |
string | yes | Echo of the requested currency. If missing, the requested currency is used. |
POST /balance¶
Returns the player's current balance. Moves no money and has no idempotency key; games call it often (for example before each bet, and when a lobby opens).
Request body:
{"player_id": "player-123", "currency": "USD", "session_id": "3f6c2a9e-8d41-4b7a-9c55-1e2f0a7b9d10"}
| Field | Type | Required |
|---|---|---|
player_id |
string | yes |
currency |
string | yes |
session_id |
string | yes |
Response: Balance response.
Errors: 404 if the player is unknown to you, 401 on a bad key or signature.
The connection test (session_id = "healthcheck")¶
19flip staff can test your endpoint from the 19flip admin panel ("test connection"). It sends one signed /balance call:
player_iddefaults to"healthcheck"andcurrencyto"USD"; staff may type a real test player and another currency instead.session_idis always"healthcheck".- Timeout 5 seconds, one attempt, no retries.
- The test passes on any
2xx. It shows the HTTP status, latency and the first 2000 characters of your body.
Recommended: after checking the key and signature, answer session_id == "healthcheck" with 200 and {"balance": 0, "currency": "<requested currency>"} even if no such player exists. Otherwise the test reports your 404 for the unknown player healthcheck as a failure.
POST /bet/debit¶
Takes a bet from the player's balance.
Request body:
{"player_id": "player-123", "amount": 1.5, "currency": "USD", "round_id": 48213, "session_id": "3f6c2a9e-8d41-4b7a-9c55-1e2f0a7b9d10", "idempotency_key": "bb-tkt-7-48213-9921", "game_id": "bingo-bongo", "transaction_id": null}
| Field | Type | Required |
|---|---|---|
player_id |
string | yes |
amount |
number | yes |
currency |
string | yes |
round_id |
integer | yes |
session_id |
string | yes |
idempotency_key |
string | yes |
game_id |
string or null | yes (may be null) |
transaction_id |
string or null | yes (may be null) |
Success response (200):
{
"transaction_id": "5b0e1c52-3f7e-4d7c-9a8e-0f5d2a61c9e4",
"balance_before": 100.0,
"balance_after": 98.5,
"status": "SUCCESS"
}
Insufficient funds (402, nothing debited):
Rules:
- Debit atomically: check the balance and debit in one database transaction, with a unique constraint on
idempotency_key. - A repeat of the same
idempotency_keyreturns the stored answer (the sametransaction_idand balances, or the same402), without debiting again. - A round can contain several debits (for example Block Boost debits on every paid action; Bingo Bongo debits once per card purchase).
- 19flip only forwards bets that pass its own checks: your account is active, the player is not blocked, and the amount is within your minimum/maximum bet. Refused bets never reach you.
POST /win/credit¶
Adds a win to the player's balance.
Request body:
{"player_id": "player-123", "amount": 12.0, "currency": "USD", "round_id": 48213, "session_id": "3f6c2a9e-8d41-4b7a-9c55-1e2f0a7b9d10", "idempotency_key": "bb-bank-7-48213-551", "game_id": "bingo-bongo", "transaction_id": null}
Fields are the same as for /bet/debit.
Success response (200):
{
"transaction_id": "9d7f3a10-6b2c-4e8a-8f41-2c6b0e9a7d55",
"balance_before": 98.5,
"balance_after": 110.5,
"status": "SUCCESS"
}
Rules:
- Always accept a valid win. Do not refuse it because the session is old, the player is offline, the round ended long ago, or the player's account is restricted for new bets. The money is already won; refusing only creates a manual case.
- Wins can arrive minutes or hours after the player left (for example Bingo Bongo pays jackpots and insurance from a background process, and keeps retrying until you confirm).
- A round can have several wins for the same player, each with its own
idempotency_key(for example a bank prize, a jackpot tier and an insurance payout). - A win can go to a player who did not place a bet in that round (Bingo Bongo credits tips to the round's host, who is one of your players).
- Rounds without a win produce no
/win/creditcall;amountis never0.
POST /bet/rollback¶
Undoes a bet: returns the debited amount to the player.
Request body:
{"player_id": "player-123", "amount": 1.5, "currency": "USD", "round_id": 48213, "session_id": "3f6c2a9e-8d41-4b7a-9c55-1e2f0a7b9d10", "original_transaction_id": "5b0e1c52-3f7e-4d7c-9a8e-0f5d2a61c9e4", "idempotency_key": "bb-rfnd-7-48213-9921", "reason": "round cancelled"}
| Field | Type | Required |
|---|---|---|
player_id |
string | yes |
amount |
number | yes |
currency |
string | yes |
round_id |
integer | yes |
session_id |
string | yes |
original_transaction_id |
string | yes |
idempotency_key |
string | yes |
reason |
string or null | yes (may be null) |
Success response (200):
{
"transaction_id": "0c4e9b27-1d8a-4f36-b2e5-7a9c3d1f6e80",
"balance_before": 98.5,
"balance_after": 100.0,
"status": "SUCCESS"
}
Rules:
- Find the original debit by
original_transaction_id, matching either yourtransaction_idor the originalidempotency_key. The idempotency key is used when the original debit timed out and 19flip never learned your id. amountequals the original debit amount.- A debit is rolled back at most once. If the original is already rolled back (for example by a manual rollback with a different key), return
200with the result of that first rollback and do not credit again. - If you have no record of the original (it never reached you, or you declined it), nothing was debited. Recommended: answer
200SUCCESSwithbalance_before == balance_after, store the rollback under itsidempotency_key, and refuse any later/bet/debitwhoseidempotency_keyequals thisoriginal_transaction_id. A404here leaves the operation open on our side for manual review. - Reasons you may see:
round cancelled(Bingo Bongo refunds cards of a cancelled round),Rollback due to error after bet_debit: ...(a game failed after your debit),manual rollbackor an operator note (19flip staff resolving an unknown outcome; the key then looks likemanual-rb-<number>).
POST /win/rollback¶
Undoes a win: takes the credited amount back from the player.
Request body:
{"player_id": "player-123", "amount": 12.0, "currency": "USD", "round_id": 48213, "session_id": "3f6c2a9e-8d41-4b7a-9c55-1e2f0a7b9d10", "original_transaction_id": "9d7f3a10-6b2c-4e8a-8f41-2c6b0e9a7d55", "idempotency_key": "7c1d2b0e8f4a4b6c9e3d5f7a1b2c4d6e8f0a1b3c5d7e9f1a2b4c6d8e0f1a3b5c", "reason": "Rollback due to error after win_credit: ..."}
Fields are the same as for /bet/rollback; original_transaction_id refers to a /win/credit.
Success response (200):
{
"transaction_id": "e2a8c4f6-0b1d-4c3e-9f5a-7d6b8e0c2a41",
"balance_before": 110.5,
"balance_after": 98.5,
"status": "SUCCESS"
}
Rules:
- Same lookup and at-most-once rules as
/bet/rollback. - The player may already have spent the win. Decide by your policy whether the balance may go negative; if you refuse, answer
402. - Rare: today only Block Boost and Mister Twister send it, and only when the game fails right after your credit. Bingo Bongo never takes a paid win back.
POST /round/finish¶
Added in 2.34.0
Flip Jet sends it after every round in which your players had LIVE bets.
Tells you that a shared game round has ended: its crash point, its time and the outcome of each of your players' bets in it. It moves no money: wins are paid with /win/credit and refunds with /bet/rollback as before. Use it for anything that depends on the round result, for example a jackpot drawn by the crash point, and to close bets that were lost (a loss has no callback of its own).
Sent only for rounds in which at least one of your players had a LIVE bet; DEMO bets are never reported. Other casinos' bets are never included.
Request body (one line on the wire; formatted here):
{
"game_id": "flip-jet",
"game_round_id": 327859,
"type": "shared",
"status": "finished",
"crash_point": "19.19",
"started_at": "2026-10-14T05:16:30.412Z",
"finished_at": "2026-10-14T05:16:46.087Z",
"bets": [
{"round_id": 5123, "player_id": "player-123", "session_id": "3f6c2a9e-8d41-4b7a-9c55-1e2f0a7b9d10", "currency": "USD", "amount": 1.5, "result": "won", "multiplier": "2.35", "win": 3.52},
{"round_id": 5124, "player_id": "player-123", "session_id": "3f6c2a9e-8d41-4b7a-9c55-1e2f0a7b9d10", "currency": "USD", "amount": 1.0, "result": "lost", "multiplier": null, "win": null}
],
"idempotency_key": "flipjet:327859:FINISH"
}
| Field | Type | Description |
|---|---|---|
game_id |
string | "flip-jet". |
game_round_id |
integer | Number of the shared game round, the same for all players. Equals game_round_id in the round's debits and credits. Not the round_id of the money callbacks. |
type |
string | "shared": a normal round. "golden_flight": a purchased solo round of one player (Golden Flight); crash_point is then the landing and bets has exactly one won entry with amount = the price. Added in 2.73.0. |
route |
string | Golden Flight only. Always aurora since 2.83.0 (earlier flights could have solar or horizon). Absent for shared rounds. |
price_mult |
integer | Golden Flight only: the price in bets, 20, 50 or 100. Absent for shared rounds. |
status |
string | finished: the round ended with a crash. interrupted: the round was stopped by a failure on our side; bets still in flight are refunded. |
crash_point |
string or null | Crash multiplier with two decimals, e.g. "1.00", "19.19", "10000.00". null when status is interrupted. |
started_at |
string or null | Takeoff time, UTC, ISO 8601 with milliseconds. null if the round was interrupted before takeoff. |
finished_at |
string | Crash (or interruption) time, UTC, ISO 8601 with milliseconds. |
bets |
array | Your players' bets that played in this round, see below. Never empty. |
idempotency_key |
string | flipjet:<game_round_id>:FINISH. The same on every retry. Unique per round, not per casino. |
Each element of bets:
| Field | Type | Description |
|---|---|---|
round_id |
integer | The bet's round_id from its /bet/debit, /win/credit and /bet/rollback. |
player_id |
string | Your player. |
session_id |
string | Session the bet was placed in. |
currency |
string | Currency of the bet. |
amount |
number | Bet amount. |
result |
string | won: cashed out (the /win/credit may still be on its way). lost: not cashed out before the crash. refunded: the round was interrupted and the bet is returned with /bet/rollback (reason ROUND_VOIDED). |
multiplier |
string or null | Cash-out multiplier of a won bet, e.g. "2.35"; null otherwise. |
win |
number or null | Win of a won bet (= the /win/credit amount); null otherwise. |
Bets that never played in the round are not listed: bets cancelled before takeoff, refused bets and late debits. Their money was already returned with /bet/rollback.
Response: any 2xx means "received". The body is not read; {"status": "OK"} is fine.
Rules:
- Deduplicate on
idempotency_key: a retry carries the same body and key. Answer2xxto a repeat without processing it again. - Order is not guaranteed relative to the money callbacks of the same round:
/round/finishmay arrive before or after the/win/creditof a cash-out made at the last moment. - It is sent once per round, about a second after the crash. If you answer
5xx, time out or cannot be reached, it is retried: 3 quick attempts as for money callbacks, then again after 1, 5, 15, 60 minutes and 3, 6, 12 hours. After that it is dropped; use the round history to fill gaps. - A
4xxanswer is final and is not retried (for example404while you have not implemented the endpoint). Nothing else depends on it. - The crash point is never sent before the round ends.
Example: one Block Boost round¶
/balance→{"balance": 100.0, "currency": "USD"}./bet/debitamount1.0→balance_after99.0./balanceand/bet/debitagain for each further paid action in the same round (sameround_id, a newidempotency_keyeach time).- At the end of the round,
/win/creditwith the total win if it is greater than0.
Example: one Bingo Bongo round¶
/balancewhen the player opens the lobby./bet/debitper card purchase (keybb-tkt-...); tips to the host are also debits with abb-tkt-...key.- After the draw, from a background process:
/win/creditfor the bank prize (bb-bank-...), jackpot tiers (bb-jpn-...,bb-jpx-...), insurance (bb-ins-...) and host tips (bb-tip-..., credited to the host'splayer_id). - If the round is cancelled:
/bet/rollbackper purchase (bb-rfnd-...,reasonround cancelled).
Example: one Flip Jet round¶
/bet/debitper bet (up to two per player per round), each with its ownround_idand the sharedgame_round_id./win/creditper cashed-out bet withmultiplier.- After the crash: one
/round/finishwithcrash_pointand all of your players' bets in the round, lost ones included.
See Game specifics for all per-game differences.