Callbacks and settlement
webhook is the full registered callback URL. The provider POSTs directly to it without appending a path. The kit serves POST /callback. The signed payload's event field distinguishes open, transaction and close. Partners own their players, balances and ledger; receiving open must not create arbitrary players.
Signature verification
Callbacks use a JSON envelope. payload is the original business JSON string.
{
"payload": "{\"event\":\"open\",\"user\":\"test-player-001\",\"user_key\":\"SESSION\"}",
"timestamp": "UNIX_SECONDS",
"nonce": "RANDOM_HEX",
"sign": "HMAC_HEX"
}
sign = HMAC-SHA256(secret, timestamp + ":" + nonce + ":" + payload), encoded as lowercase hexadecimal. Verify the exact received payload string, without parsing and reserializing it. Allow at most 300 seconds of clock difference and compare signatures in constant time. Parse business data only after verification.
Dispatch by the signed event, never an unsigned request header. Every delivery is signed. nonce is not a substitute for business idempotency: duplicate deliveries are valid and must be deduplicated by business identity.
open
The payload contains event="open", user and user_key. user_key identifies one wallet session. Lock the player row, verify that no other unclosed session exists, and save the session and initial balance. Repeated open requests return the first confirmed balance; do not fund it again.
{"success": true, "message": 10000000}
transaction
{
"event": "transaction",
"transaction_id": "123456",
"user_key": "SESSION",
"user": "test-player-001",
"game_code": "baccarat",
"amount": -100,
"balance": 9999900,
"timestamp": 1791900000
}
amount is a signed integer. The new balance must equal the old balance plus amount. Validate that user_key is the current session. Use partner, player and transaction_id as the unique business identity. Repeated events return the original result. A reused ID with conflicting amounts or payload must be rejected and reconciled.
Commit balance, ledger entry and deduplication record in one database transaction. A lost HTTP response must not cause duplicate booking. Check historical events before current-session validation so old transactions can safely retry after wallet closure.
close
The payload contains event="close", user, user_key and balance. Match the current session and reconcile the local balance. A late old close must not close a newer session. Repeating the same close returns success.
{"event":"close","user":"test-player-001","user_key":"SESSION","balance":9999900}
transaction and close acknowledge with HTTP 200 and {"success":true,"message":"ok"}. Errors or non-JSON responses do not acknowledge delivery.
Failure handling
transaction and close are persisted in the provider's callback queue before signed delivery in session order. Failed deliveries use increasing backoff. After 20 failures, the event enters dead-letter state and blocks subsequent events; administrators can retry the original after investigation. Persisted events resume after a backend restart and are signed again. Deduplicate by business ID. open remains synchronous.
The provider does not maintain an independent wallet ledger or wallet session table, nor a GameServer disk replay queue. Callback persistence is not an atomic end-to-end settlement guarantee: a crash before a game message reaches the queue still requires manual reconciliation.
Keep an independent ledger rather than relying on short-lived provider game logs. On balance mismatch, retain the session, suspend related money operations and provide event IDs. Do not overwrite balances or mark failed events as successful.
After a process crash, retain unclosed local wallet state and reconcile logs manually. There is currently no automatic provider recovery API. Verify restart handling, lost responses and settlement ordering before opening games to players.
Callback address
For https://operator.example/callback, all three events use that exact URL. Return JSON directly without a browser login session or login redirect. The outbound-IP allowlist restricts API calls originating from your server; callback receivers instead verify platform signatures.
Field types and amount units
All events use POST and application/json for requests and responses. Business fields are inside the payload string, not at envelope top level.
| Envelope field | Type | Meaning |
|---|---|---|
| payload | string | Required: original business JSON, preserved for verification |
| timestamp | string | Required: Unix seconds for this delivery signature |
| nonce | string | Required: random signing text |
| sign | string | Required: lowercase hexadecimal HMAC-SHA256 |
| Business field | Type | Event and meaning |
|---|---|---|
| event | string | All: open, transaction or close |
| user | string | All: stable partner player code |
| user_key | string | All: wallet session ID; validate together with player |
| transaction_id | string | transaction: unique transaction ID; preserve as text, never floating point |
| game_code | string | transaction: game identifier |
| amount | integer (int64) | transaction: signed balance change, negative deducts and positive adds |
| balance | integer (int64) | transaction / close: resulting or closing balance for reconciliation |
| timestamp | integer | transaction: Unix seconds of transaction; distinct from the envelope string timestamp |
Amounts are raw integers: 1000 raw units = 1.00 coin. Display exchange rates do not convert callback currency or introduce decimal amounts. open's message must be a nonnegative int64 JSON number, not a string. Use an int64-capable JSON parser. JavaScript Number loses precision beyond its safe integer range and must not handle large ledger amounts.
Acknowledgements and failures
{"success": true, "message": "ok"}
{"success": false, "message": "wallet balance mismatch"}
Failure messages are partner-defined, not platform error codes. Reject invalid signatures. Do not return success=true for uncommitted transactions, missing players, session conflicts or balance mismatches. Never acknowledge first and book asynchronously.
transaction / close delivery succeeds only with HTTP 200, valid JSON and success=true. message is not checked. Requests time out after 8 seconds, responses are limited to 64 KiB, and redirects are not followed. Timeouts, non-200 responses, invalid JSON and success=false all fail. Exponential retry delay reaches approximately 256 seconds; the 20th failure becomes dead-letter. The business payload stays unchanged, but timestamp, nonce and sign are regenerated each attempt.
open must promptly return the confirmed initial balance and does not enter the automatic outbox retry queue. Failure prevents wallet opening. Retain business events, processing results and reconciliation evidence; exclude signatures and secrets from ordinary logs.