Wallet Endpoints
Operators integrate with the platform by implementing these endpoints:
Authentication
| Endpoint | When Called | Purpose |
|---|---|---|
POST /v1/session | Widget load | Exchange session_request_id for a JWT |
POST /v1/auth | Session check | Validate session and return player info |
Transactions
| Endpoint | When Called | Purpose |
|---|---|---|
POST /v1/withdrawals | Bet placement | Debit stake from bettor's balance |
POST /v1/deposits | Settlement | Credit payout to bettor's balance |
DELETE /v1/rollbacks/{id} | Withdrawal compensation | Reverse a failed withdrawal |
All transaction endpoints must be idempotent — the platform may retry on network failures.
The platform calls DELETE /v1/rollbacks/{id} to reverse a withdrawal that must not remain. This happens in two cases:
- A later step of bet placement failed, after the withdrawal succeeded.
- The outcome of the withdrawal is unknown. The platform received no answer, a timeout, or a 5xx response.
The second case is the common one. The operator can receive a rollback for a withdrawal that it has not yet completed.
The {id} is the value the platform sent as id in the body of the original POST /v1/withdrawals request. It is not the id the operator returns in the withdrawal response. The platform cannot use the returned value, because a rollback usually follows a lost or failed response.
Rollbacks are never called for deposits or settlements — only for withdrawals that need to be reversed.
If the withdrawal was applied, the operator must credit the player's balance back by the original withdrawal amount.
If the operator has no withdrawal for that {id}, it must not credit the balance. Answer 404 or 409. Crediting a withdrawal that never happened leaves the balance wrong. The platform retries the rollback, so a withdrawal that completes later is still reversed.
| Outcome | Amount | Meaning |
|---|---|---|
| Won | stake × odds | Bettor's winnings |
| Lost | 0 | No payout — the platform still sends the call so the operator can close the bet |
| Voided / Refunded | stake | Full stake returned |
Negative amounts are never sent. Operators must handle zero-amount deposits as a settlement notification, not as a no-op.
Request Signing (HMAC-SHA256)
Every request from the platform includes an X-Payload-Signature header containing an HMAC-SHA256 signature of the raw request body. Operators must verify this signature before processing.
Algorithm
signature = HMAC-SHA256(request_body, shared_secret)
header = hex_encode(signature)
Headers Sent
| Header | Value |
|---|---|
X-Payload-Signature | Hex-encoded HMAC-SHA256 of the request body |
X-Timestamp | ISO 8601 UTC timestamp (for replay prevention) |
X-Nonce | UUID v4 (for replay prevention) |
Content-Type | application/json |
Verification Example
Given:
- Body:
{"amount":"10.50"} - Secret:
test-secret - Expected signature:
37f9186da8bef5457f94d56d1c76dc37f8c8854e35751cf7eb795da23d593329
Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func VerifySignature(body []byte, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}
func main() {
body := []byte(`{"amount":"10.50"}`)
secret := "test-secret"
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
sig := hex.EncodeToString(mac.Sum(nil))
fmt.Println(sig)
// Output: 37f9186da8bef5457f94d56d1c76dc37f8c8854e35751cf7eb795da23d593329
}
Node.js
const crypto = require('crypto');
function verifySignature(body, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature),
);
}
// Test vector
const body = '{"amount":"10.50"}';
const secret = 'test-secret';
const sig = crypto.createHmac('sha256', secret).update(body).digest('hex');
console.log(sig);
// Output: 37f9186da8bef5457f94d56d1c76dc37f8c8854e35751cf7eb795da23d593329
Python
import hashlib
import hmac
def verify_signature(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
# Test vector
body = b'{"amount":"10.50"}'
secret = "test-secret"
sig = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
print(sig)
# Output: 37f9186da8bef5457f94d56d1c76dc37f8c8854e35751cf7eb795da23d593329
Replay Prevention
Operators should also validate:
- Timestamp — reject requests where
X-Timestampis older than 5 minutes - Nonce — store seen nonces and reject duplicates within the timestamp window
See Also
- Authentication Flow — how the two-JWT architecture works
- Operator API Reference — full request/response schemas