Overview
Your server talks to our API with an API key. To start a game, your server opens a session for one of your players and a game (identified by its service id), and embeds the returned launchUrl in an iframe. While the player plays, we call your wallet to read the balance, debit stakes and credit wins. Your players keep a single balance in your casino.
All amounts are integers in minor units of the session currency (cents for EUR/USD). Responses are JSON: { "ok": true, "data": … } or { "ok": false, "error": { "code", "message" } }.
Authentication
Create keys in the partner portal. Sandbox keys start with tcg_test_, live keys with tcg_live_. The key decides the environment; keys are shown once — store them as secrets on your server and never in a browser.
Authorization: Bearer tcg_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXList games
GET /partner/games
{ "ok": true, "data": [
{ "serviceId": 1000, "slug": "integration-test", "title": "Integration test game",
"rtp": 98, "volatility": "low", "minStake": 1, "maxStake": 1000000,
"launchable": true, "sandboxOnly": true, "rate": 0 }
] }rate is your cost rate for the game in live play (share of GGR). Sandbox keys also see unreleased games so you can test them early.
Open a session
POST /partner/sessions
{ "serviceId": 1000, "playerId": "your-player-123", "currency": "EUR",
"locale": "en", "lobbyUrl": "https://your-casino.example/lobby" }
{ "ok": true, "data": {
"sessionId": "cm…", "serviceId": 1000, "environment": "SANDBOX",
"expiresAt": "2026-10-25T14:00:00.000Z",
"launchUrl": "https://thechartgames.com/play/1000?session=…" } }playerIdis your own player reference (up to 100 characters). We send it back in every wallet call.- Embed
launchUrlin an iframe (or open it in a new window). It is valid for the session lifetime (2 hours by default) and is tied to this player only. - Open a new session each time the player opens a game.
Seamless wallet
Expose one HTTPS endpoint and enter it in the portal (separately for sandbox and live). We send POST requests with a JSON body:
{ "requestId": "uuid", "action": "debit", "sessionId": "cm…",
"playerId": "your-player-123", "currency": "EUR", "serviceId": 1000,
"roundId": "cm…", "amount": 100, "transactionId": "cm…:bet" }| action | When | What you do |
|---|---|---|
balance | Game start | Return the player's balance. |
debit | Before each round | Take amount (the stake). Refuse with INSUFFICIENT_FUNDS if the player cannot afford it. |
credit | End of each round | Add amount (the win, can be 0). roundFinished: true. |
rollback | Only if a debit may have failed | Reverse referenceTransactionId if you processed it; otherwise just answer. |
Answer HTTP 200 with the new balance, or an error:
{ "balance": 9900 }
{ "error": { "code": "INSUFFICIENT_FUNDS", "message": "Not enough money" } }Other useful codes: SESSION_EXPIRED, PLAYER_BLOCKED, LIMIT_REACHED. Answer within 8 seconds.
Verifying our signature
Every request carries x-tcg-timestamp (Unix seconds) and x-tcg-signature = hex HMAC-SHA256 of `${timestamp}.${rawBody}` with your wallet secret (generated in the portal, shown once). Reject requests with a wrong signature or a timestamp older than 5 minutes.
// Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody, timestamp, signature, secret) {
const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
return fresh && expected.length === signature.length &&
timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}<?php // PHP
function verify($rawBody, $ts, $sig, $secret) {
$expected = hash_hmac('sha256', $ts . '.' . $rawBody, $secret);
return abs(time() - (int)$ts) < 300 && hash_equals($expected, $sig);
}Idempotency, rollbacks and retries
- transactionId is unique per money movement (
…:bet,…:win,…:rollback). If you receive one you already processed, do not move money again — just return the current balance. - If your wallet does not answer a debit, the round is cancelled and we send a
rollbackfor it in case you did take the stake. - If your wallet does not confirm a credit, we keep the round open and send the same credit again every minute (up to 20 times) until you confirm it. Wins are never lost.
- The game screen sends its own idempotency key per round, so a player double-clicking or reconnecting never plays the same round twice.
Rounds and reports
GET /partner/rounds?page=1&playerId=your-player-123&status=SETTLED
GET /partner/rounds/{roundId} // full result, server seed and its hash
GET /partner/usage?month=2026-10 // live bets, wins, GGR, rate and cost per gameRound statuses: SETTLED (done), FAILED (stake refused or not charged), CREDIT_PENDING (win waiting for your wallet to confirm). Every round can be verified with its server seed — see Fairness.
Errors
| HTTP | code | Meaning |
|---|---|---|
| 400 | VALIDATION_FAILED | A field is missing or wrong (see fields). |
| 401 | UNAUTHENTICATED | Missing, wrong or revoked API key. |
| 403 | FORBIDDEN | Live access not active yet, or the account is suspended. |
| 409 | CONFLICT | Wallet URL or secret not set for this environment. |
| 422 | GAME_UNAVAILABLE | Unknown, unreleased or retired service id. |
| 429 | RATE_LIMITED | More than 600 API requests per minute. |
Sandbox and test game
Sandbox keys work as soon as you sign up. Service 1000 is our integration test game: one coin flip per round (a match pays 1.96×), so you can check the whole flow — balance, debit, credit, rollback and retries — against your sandbox wallet. In the portal you can test your wallet connection and launch the test game for any player id. Sandbox rounds are never billed.
Go-live checklist
- Your sandbox wallet passes the connection test and you have played settled rounds.
- Repeated
transactionIds do not move money twice;INSUFFICIENT_FUNDSis returned when needed. - Our team approves your account (we may ask for your licence details).
- You create a live key, set your live wallet URL and secret, and run the connection test again.
Questions? Contact our integration team.