Skip to main content

Wallet Endpoints

Operators integrate with the platform by implementing these endpoints:

Authentication​

EndpointWhen CalledPurpose
POST /v1/sessionWidget loadExchange session_request_id for a JWT
POST /v1/authSession checkValidate session and return player info

Transactions​

EndpointWhen CalledPurpose
POST /v1/withdrawalsBet placementDebit stake from bettor's balance
POST /v1/depositsSettlementCredit payout to bettor's balance
DELETE /v1/rollbacks/{id}Withdrawal compensationReverse a failed withdrawal

All transaction endpoints must be idempotent — the platform may retry on network failures.

When rollbacks happen

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.

Deposit amounts by settlement outcome
OutcomeAmountMeaning
Wonstake × oddsBettor's winnings
Lost0No payout — the platform still sends the call so the operator can close the bet
Voided / RefundedstakeFull 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​

HeaderValue
X-Payload-SignatureHex-encoded HMAC-SHA256 of the request body
X-TimestampISO 8601 UTC timestamp (for replay prevention)
X-NonceUUID v4 (for replay prevention)
Content-Typeapplication/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:

  1. Timestamp — reject requests where X-Timestamp is older than 5 minutes
  2. Nonce — store seen nonces and reject duplicates within the timestamp window

See Also​