Wallet Certification Suite
curl --request GET \
--url https://103.77.224.40/tbs/testimport requests
url = "https://103.77.224.40/tbs/test"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://103.77.224.40/tbs/test', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://103.77.224.40/tbs/test",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://103.77.224.40/tbs/test"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://103.77.224.40/tbs/test")
.asString();require 'uri'
require 'net/http'
url = URI("https://103.77.224.40/tbs/test")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_bodyWallet Test
Wallet Certification Suite
Runs the wallet certification suite against your wallet implementation and returns a report that lists every scenario with its result.
Wallet Certification Suite
curl --request GET \
--url https://103.77.224.40/tbs/testimport requests
url = "https://103.77.224.40/tbs/test"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://103.77.224.40/tbs/test', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://103.77.224.40/tbs/test",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://103.77.224.40/tbs/test"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://103.77.224.40/tbs/test")
.asString();require 'uri'
require 'net/http'
url = URI("https://103.77.224.40/tbs/test")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body
GET /tbs/test
The certification suite is a fixed list of scenarios that exercise the Wallet API exactly as RGS does in
production: it opens sessions, places wagers, pays wins, rolls wagers back and sends invalid requests.
Every scenario checks the HTTP status, the response body and, for rejected requests, that the player’s
balance was not changed.
Run it against your test environment as often as you need. Each call is a fresh run, nothing is cached.
Free bet and tournament flows are not part of the suite.
Preconditions
Before running the suite make sure that in your wallet:- the player behind
player_tokenexists and can open a session forgame_uuid; - the player’s currency equals
currency; - the player’s balance is at least 100 (the largest single wager is 1, one scenario wagers the whole balance and rolls it back immediately);
- the
X-Auth-Tokenvalue you pass asauthorization_headeris accepted, and any other value is rejected.
roundId and transactionId values, so the player’s history does not affect the result.
Query Parameters
| Name | Required | Description |
|---|---|---|
base_url | yes | Root URL of the wallet under test, for example https://wallet.example.com. Must start with http:// or https://. |
authorization_header | yes | Value sent as the X-Auth-Token header in every request. |
player_token | yes | Player token exchanged for a session with GET /session. |
currency | yes | ISO 4217 currency of the player. |
game_uuid | yes | Game identifier passed to /session, /balance, /credit-debit, /debit and /credit/rollback. |
only | no | Comma-separated scenario ids to run, for example CD-01,RB-03. Default: all. |
fail_fast | no | true stops after the first failed scenario. Default false. |
format | no | text (default) or json. |
GET /tbs/test/scenarios returns the current list of scenarios with ids and descriptions.
Response 200 OK
Text format:
════════ TBS WALLET CERTIFICATION REPORT ════════
suite version : 2.0
target : https://wallet.example.com
currency : USD
game uuid : b23e45a7-9be8-d312-56a4-174000426614
started at : 2026-09-17T14:48:48.291Z
duration : 1834 ms
result : FAILED (39 passed, 1 failed, 40 total)
[PASS] SES-01 Session: open session returns player, session and currency 22 ms
[PASS] SES-02 Session: repeated calls create distinct sessions for the same player 25 ms
...
[FAIL] RB-03 Rollback: repeated rollback is rejected with INVALID_TRANSACTION 9 ms
step : rollback, second attempt
reason : expected HTTP 400 with error_code=INVALID_TRANSACTION, got HTTP 200
request : POST https://wallet.example.com/credit/rollback
{"playerId":"p-1","sessionId":"s-1","currency":"USD","gameUuid":"...","amount":1,...}
response : HTTP 200
{"balance":998.00}
═════════════════════════════════════════════════
format=json) returns the same data as an object:
{
"suiteVersion": "2.0",
"baseUrl": "https://wallet.example.com",
"currency": "USD",
"gameUuid": "b23e45a7-9be8-d312-56a4-174000426614",
"startedAt": "2026-09-17T14:48:48.291Z",
"durationMs": 1834,
"total": 40,
"passed": 39,
"failed": 1,
"success": false,
"results": [
{
"id": "RB-03",
"name": "Rollback: repeated rollback is rejected with INVALID_TRANSACTION",
"description": "Wager 1, rollback succeeds, the same rollback again returns HTTP 400 ...",
"passed": false,
"durationMs": 9,
"failure": {
"step": "rollback, second attempt",
"reason": "expected HTTP 400 with error_code=INVALID_TRANSACTION, got HTTP 200",
"request": "POST https://wallet.example.com/credit/rollback",
"requestBody": "{...}",
"responseStatus": 200,
"responseBody": "{\"balance\":998.00}"
}
}
]
}
Errors
| HTTP Status | Body | Meaning |
|---|---|---|
400 | { "error_code": "INVALID_PARAMETER", "message": "base_url must not be blank" } | A query parameter is missing or invalid. No scenario was executed. |
request failed: HttpRequestTimeoutException.
Scenarios
Every rejected request must returnHTTP 400 with the listed error_code, and the player’s balance must
stay unchanged. Each invalid request contains exactly one invalid field, so the order in which your wallet
validates fields does not matter, except for an unknown playerId combined with a valid sessionId, where
both INVALID_PLAYER_ID and INVALID_SESSION are accepted.
Session and balance
| Id | Scenario | Expected |
|---|---|---|
| SES-01 | GET /session with a valid token | 200, non-empty playerId and sessionId, currency equals the player’s currency |
| SES-02 | GET /session twice with the same token | same playerId, different sessionId, both accepted by GET /balance |
| SES-03 | GET /session with an unknown token | INVALID_TOKEN |
| SES-04 | GET /session with a wrong X-Auth-Token | NOT_AUTHORIZED |
| BAL-01 | GET /balance for a valid session | 200, balance >= 0 |
| BAL-02 | GET /balance with an unknown sessionId | INVALID_SESSION |
| BAL-03 | GET /balance with a wrong X-Auth-Token | NOT_AUTHORIZED |
Credit-debit
| Id | Scenario | Expected |
|---|---|---|
| CD-01 | CREDIT_DEBIT_SPIN, credit 1, debit 0, roundStarted=true | balance decreases by 1 |
| CD-02 | CREDIT_DEBIT_SPIN, credit 1, debit 10, roundStarted=true, roundFinished=true | balance increases by 9 |
| CD-03 | CREDIT_DEBIT_PURCHASE, credit 1, debit 10, round started and finished | balance increases by 9 |
| CD-04 | credit 0.25, debit 1.75 | balance increases by exactly 1.50 |
| CD-05 | the same request sent twice (same transactionId) | 200 both times, wager applied once |
| CD-06 | credit equal to the whole balance, then rollback | 200, balance 0, then restored |
| CD-07 | credit above the balance | INSUFFICIENT_FUNDS |
| CD-08 | negative creditAmount | BET_NOT_ALLOWED |
| CD-09 | negative debitAmount | BET_NOT_ALLOWED |
| CD-10 | unknown playerId | INVALID_PLAYER_ID or INVALID_SESSION |
| CD-11 | unknown sessionId | INVALID_SESSION |
| CD-12 | currency different from the player’s | INVALID_PLAYER_CURRENCY |
| CD-13 | second request with roundStarted=true in the same roundId | ROUND_ALREADY_STARTED |
| CD-14 | request in a round already closed with roundFinished=true | ROUND_ALREADY_FINISHED |
| CD-15 | wrong X-Auth-Token | NOT_AUTHORIZED |
Debit
Every debit scenario first places a wager of 1 with/credit-debit in a new round.
| Id | Scenario | Expected |
|---|---|---|
| DB-01 | DEBIT_SPIN 10 (roundFinished=false), then DEBIT_JACKPOT 100 (roundFinished=true) | balance increases by 109 |
| DB-02 | DEBIT_SPIN 0 with roundFinished=true | 200, balance unchanged |
| DB-03 | the same debit sent twice (same transactionId) | 200 both times, win paid once |
| DB-04 | negative amount | BET_NOT_ALLOWED |
| DB-05 | unknown playerId | INVALID_PLAYER_ID or INVALID_SESSION |
| DB-06 | unknown sessionId | INVALID_SESSION |
| DB-07 | currency different from the player’s | INVALID_PLAYER_CURRENCY |
| DB-08 | debit into a roundId that never received a wager | INVALID_TRANSACTION |
| DB-09 | debit into a round closed with roundFinished=true | ROUND_ALREADY_FINISHED |
| DB-10 | wrong X-Auth-Token | NOT_AUTHORIZED |
Rollback
Every rollback scenario first places a wager of 1 with/credit-debit in a new round.
| Id | Scenario | Expected |
|---|---|---|
| RB-01 | rollback with the wager’s roundId, transactionId and amount | 200, balance restored |
| RB-02 | rollback of a roundId and transactionId that never existed | INVALID_TRANSACTION |
| RB-03 | the same rollback sent twice | second call INVALID_TRANSACTION |
| RB-04 | rollback after a win was paid in the round | INVALID_TRANSACTION |
| RB-05 | rollback with an amount different from the wager | INVALID_TRANSACTION |
| RB-06 | rollback in a round closed with roundFinished=true | INVALID_TRANSACTION |
| RB-07 | unknown sessionId | INVALID_SESSION |
| RB-08 | wrong X-Auth-Token | NOT_AUTHORIZED |
Request
GET /tbs/test?base_url=https://wallet.example.com&authorization_header=5QhbQdQr4EMMNnY79qNPhJEUpXv3vdvp&player_token=abcd1234¤cy=USD&game_uuid=b23e45a7-9be8-d312-56a4-174000426614