GameServer Integration API
Reference for the current local slot provider implementation. There are two interfaces: browser-to-GameServer launch and gameplay APIs, and GameServer-to-integrator session and wallet callbacks. The contract documented here describes the local wallet integration.
Quick Start
The default Docker GameServer URL is http://localhost:8090. Use your public HTTPS URL for remote browsers.
All monetary values are integer cents: 100 means 1.00.
Demo Play
http://localhost:8090/launch?mode=demo&exit=%2Findex.html
Each demo launch creates a fresh token, isolated demo player, and dummy balance of 100000 cents by default. No account, incoming token, signature, or running integrator is required. Launch, state, spin, and close never call the integrator or modify a casino wallet. Demo balances and rounds persist in SQLite across restarts.
Real-Money Play
- Create an active session token in your casino backend, associated with a player, currency, game, and wallet.
- Implement the callbacks below and configure GameServer's integrator base URL, node ID, and shared signing salt.
- Open
/launch?mode=real&token=YOUR_SESSION_TOKEN&exit=ENCODED_RETURN_URLin the browser or an iframe. - GameServer fetches the session, redirects to its game UI, and calls your wallet for bets and nonzero wins.
To embed the UI, grant fullscreen permission on the iframe:
<iframe src="https://games.example/launch?token=YOUR_SESSION_TOKEN"
title="Game" allow="fullscreen" allowfullscreen></iframe>
Launch API
GET /launch returns HTTP 302 with a location of /game.html?token=... on success.
| Query Parameter | Behavior |
|---|---|
mode | demo enables dummy-money play (case-insensitive). Omitted, real, cash, and any other value use the real integrator flow. |
token | Use your casino-issued session token for real play. If omitted, GameServer generates a 32-character uppercase hexadecimal token; your backend would still need to recognize it. Demo ignores this parameter and generates its own token. |
exit | Optional return URL, URL-encoded in the query. Stored in the session; the game UI navigates the top window there after close. Defaults to / in the UI. |
game | Demo game identifier, defaulting to server configuration. In real mode, the game comes from the integrator session response, not this query parameter. |
sign | Optional launch signature. If validation is enabled and a signature is supplied, a mismatch returns 401 ACCESS_DENIED. Missing signatures are currently accepted even when validation is enabled. |
nodeId, player, currency, lang, platform | Do not override server or session settings. Supplied query values still participate in launch signing. Demo currency comes from server configuration. |
Real-mode integrator session failures with a 4xx status are passed through. A parsed session response with an empty
player or currency returns 502 INTERNAL_ERROR. Other session failures can create a fallback local player
with zero starting balance; this does not enable callback-free demo mode. Real gameplay still requires the integrator.
Relaunching a real player closes that player's other local sessions and synchronizes the local balance when the integrator supplies one. Currently all game identifiers open the same UI and use the same slot engine.
Browser Game API
Paths are relative to GameServer. POST requests use Content-Type: application/json.
Tokens identify sessions; these endpoints do not use the callback signing scheme. Spins also require a single-use
credential returned by state. Keep tokens and credentials private and use HTTPS. This limits abuse, not all automated play.
GET /api/game/state?token=TOKEN
{
"token": "TOKEN",
"playerName": "player123",
"currency": "EUR",
"game": "goldrush",
"rounds": 0,
"totalBetCents": 0,
"totalWinCents": 0,
"balanceCents": 100000,
"mode": "real",
"spinCredential": "SINGLE_USE_CREDENTIAL",
"retryAfterMs": 0,
"minimumIntervalMs": 1500,
"exitUrl": "/index.html"
}
Demo returns mode: "demo". Unknown or closed sessions return 404 with
{"error":"session not found"}. Real state calls integrator /session to validate activity:
upstream 401/404 become 401 PLAYER_NOT_FOUND; other failures become 503 INTEGRATOR_UNAVAILABLE.
The returned balance is the local ledger balance, not refreshed from this validation response.
POST /api/game/spin
{"token":"TOKEN","betCents":200,"spinCredential":"SINGLE_USE_CREDENTIAL"}
Example successful response (reels, payouts, and IDs vary):
{
"round": 1,
"reels": ["symbol-a", "symbol-b", "symbol-c"],
"winCents": 0,
"winLines": [],
"balanceCents": 99800,
"betTransactionId": 1700000000001,
"winTransactionId": null
}
Demo uses the same response shape, with both transaction IDs null. Actual reels are display symbols.
Fetch state before the next spin to obtain its credential and honor retryAfterMs. Credentials expire
after two minutes and are consumed when a spin is admitted, even if the bet later fails. Repeated state reads reuse
the current unconsumed credential. State responses are marked Cache-Control: no-store.
Only one spin per player can be in flight, across that player's sessions in this server process.
There is no client-supplied idempotency key; do not retry a timed-out spin automatically.
| Status | Spin Error |
|---|---|
| 400 | {"error":"bet must be > 0"} |
| 404 | {"error":"session not found"} |
| 403 | INVALID_SPIN_CREDENTIAL: missing, expired, wrong-session, or already-consumed credential. |
| 409 | SPIN_IN_PROGRESS: another spin for this player is still running. |
| 429 | SPIN_TOO_FAST, SPIN_RATE_LIMITED, or AUTOMATION_BLOCKED. Protection errors include retryAfterMs; positive delays also set Retry-After in seconds. |
| 402 | INSUFFICIENT_FUNDS, or DEMO_SESSION_UNAVAILABLE_OR_INSUFFICIENT_FUNDS if the atomic demo debit fails. |
| 401, 402, 403, 404, 429 | Integrator bet rejection, returned with its code/message; fallback code is BET_REJECTED. |
| 502 | BET_FAILED_REFUNDED: a refund was attempted after another bet failure. This code does not guarantee the refund succeeded. |
POST /api/game/close
{"token":"TOKEN"}
Response: {"closed":true}
Repeated close succeeds for an already-closed session. Blank tokens return 400, unknown tokens return 404.
Real mode calls integrator /session/close; failures other than 401/404 return 503 with
{"error":"Could not close casino session"}. Demo closes locally. The API does not redirect;
the browser UI follows the stored exit URL after a successful response.
GET /api/game/log?token=TOKEN&limit=5
Returns transaction records newest first for an active session. Fields include id, providerTxId,
sessionToken, playerId, game, gameRound, currency,
type, amountCents, status, httpStatus, externalTxId,
balanceAfterCents, requestUrl, requestBody, responseBody,
errorMessage, and createdAt. Demo returns an empty array. Unknown or closed sessions return 404.
Integrator Callbacks
Your casino backend implements these paths under Integrator__BaseUrl.
All calls carry a signature, including close. Success is determined by HTTP 2xx, not a JSON success flag.
Return JSON and use a non-2xx status for errors.
GET {BaseUrl}/session
Query parameters: nodeId, token, sign. Called on real launch and real state refresh.
{"player":"player123","currency":"EUR","game":"goldrush","balance":100000}
player and currency must be nonempty strings. Supply game as a string and
balance as an integer number of cents. A new local player starts at zero if balance is absent.
POST {BaseUrl}/bet, /win, /refund
Body encoding: application/x-www-form-urlencoded, not JSON.
| Field | Description |
|---|---|
amount | Integer cents to debit (bet), credit (win), or refund. |
currency, player, game, token | Values from the real session. |
date | Unix timestamp in milliseconds, serialized as text. |
gameId | Round number starting at 1, not the game identifier. |
id | Provider transaction ID. Wins have a new ID; refunds reuse the original bet ID. |
nodeId | Configured node ID, not the launch query value. |
betSessionId, betTransactionId | Win only: original session token and bet provider transaction ID. |
sign | Signature of all non-null request parameters except sign. |
Example form (replace SIGNATURE with a computed signature):
amount=200¤cy=EUR&date=1700000000000&game=slot&gameId=1&id=1700000000001&nodeId=1&player=player123&token=TOKEN&sign=SIGNATURE
Recommended success body:
{"transactionId":"wallet-tx-123","balance":99800}
transactionId is read as text and balance as integer cents, then recorded in the transaction log.
A bet debits the casino wallet; a win credits it; a refund reverses a bet. Implement idempotency per operation and
provider transaction ID, and verify the token/player/currency mapping before changing a wallet.
Example error body with a non-2xx HTTP status:
{"code":"INSUFFICIENT_FUNDS","message":"Not enough balance"}
No win callback is sent for zero wins. For bet errors outside 401/402/403/404/429, GameServer attempts one refund. There is no background retry queue. Failed wins are logged, but local winnings are still applied and spin returns success; reconcile these failures before treating this provider as production-ready.
POST {BaseUrl}/session/close
Parameters nodeId, token, and sign are in the query string, with an empty body.
Return a 2xx status on success. A 401 or 404 also allows the local session to close. The response body is not parsed.
Signing
- Exclude the
signfield (case-insensitive) and fields whose values are null. - Sort parameter names using ordinal, case-sensitive order.
- Concatenate the raw, decoded values only, without names or separators. Include empty-string values.
- Append the shared salt, encode as UTF-8, and calculate SHA-1. Output lowercase hexadecimal.
- URL-encode the parameters for transmission after calculating the signature.
Example Node.js signing function, for use in your trusted backend:
const { createHash } = require('node:crypto');
function sign(parameters, salt) {
const values = Object.entries(parameters)
.filter(([key, value]) => key.toLowerCase() !== 'sign' && value != null)
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
.map(([, value]) => String(value))
.join('');
return createHash('sha1').update(values + salt, 'utf8').digest('hex');
}
const parameters = { nodeId: '1', token: 'TOKEN' };
parameters.sign = sign(parameters, process.env.INTEGRATOR_SALT);
For a launch signature, include every query parameter that will actually be sent, except sign. Callback signatures use the server's configured salt. Do not expose that salt in a production browser.
Configuration
Set these environment variables in the gameserver deployment:
| Variable | Purpose |
|---|---|
Integrator__BaseUrl | GameServer callback base URL, e.g. http://host.docker.internal:8080/gameserverAPI in the supplied Compose setup. |
Integrator__NodeId | Merchant/node identifier. Compose uses 1. |
Integrator__Salt | Shared signing secret. Replace the development value before deployment. |
Integrator__TimeoutSeconds | HTTP timeout; default 15 seconds. |
Integrator__ValidateIncomingSignature | Default false. Verifies supplied real-launch signatures only; does not require a signature. |
Database__Path | SQLite storage path; Compose uses /data/gameserver.db on a persistent volume. |
Game__DemoBalanceCents | Dummy balance per demo launch, default 100000 cents; negative values are clamped to zero. |
Game__DefaultCurrency | Demo and fallback currency; default EUR. |
Game__DefaultGame | Demo and fallback game ID; default goldrush. |
SpinProtection__MinimumIntervalMs | Minimum time between admitted spins per player; default 1500 milliseconds. |
SpinProtection__MaxSpinsPerMinute | Maximum admitted spins per player in a fixed 60-second window; default 30. |
SpinProtection__ViolationThreshold | Credential, concurrency, or timing violations in a fixed minute before blocking; default 10. |
SpinProtection__BlockSeconds | Temporary player block duration; default 60 seconds, constrained to 1 through 86400. |
Game__DefaultBalanceCents does not seed real balances or configure demo funds; use
Game__DemoBalanceCents for demo funds and the integrator session balance for real funds.
Operations and Current Limits
GET /healthz returns {"ok":true,"at":"UTC timestamp"}.
It is a liveness check, not an integrator readiness check.
Diagnostic endpoints: GET /api/admin/players, /sessions?limit=100,
/transactions?token=TOKEN&type=BET&limit=200, /transactions/{id},
/launches?limit=100, and /stats. The sessions, launches, and transactions limits shown
are defaults; transaction token/type filters are optional. POST /api/admin/query accepts raw SQL text
beginning with SELECT or WITH; this prefix check is not a security boundary. Database browser.
Protect admin routes with authentication and network restrictions before exposing this server publicly. No authentication is built into the admin APIs. Logs may contain session tokens and request/response bodies. Restrict token-based game APIs, use HTTPS, and validate return URLs in your integration.
The current implementation uses one shared slot UI and engine, a local balance ledger, and synchronous callbacks. Spin protection applies to real and demo players; timing, counters, credentials, and temporary blocks persist in SQLite. Violations are logged without credentials. Demo relaunches create new players, so player limits alone cannot prevent demo-session churn. The per-player in-flight guard supports one server process only; do not deploy multiple replicas sharing a wallet without a distributed guard. Automation operating the UI or obtaining credentials remains possible. There is no CAPTCHA or behavioral scoring engine. The server does not provide automatic failed-win reconciliation or guaranteed refunds. Do not blindly retry spin requests after a timeout: a wallet debit may already have occurred.