GameServer Home

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

  1. Create an active session token in your casino backend, associated with a player, currency, game, and wallet.
  2. Implement the callbacks below and configure GameServer's integrator base URL, node ID, and shared signing salt.
  3. Open /launch?mode=real&token=YOUR_SESSION_TOKEN&exit=ENCODED_RETURN_URL in the browser or an iframe.
  4. 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 ParameterBehavior
modedemo enables dummy-money play (case-insensitive). Omitted, real, cash, and any other value use the real integrator flow.
tokenUse 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.
exitOptional 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.
gameDemo game identifier, defaulting to server configuration. In real mode, the game comes from the integrator session response, not this query parameter.
signOptional 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, platformDo 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.

StatusSpin Error
400{"error":"bet must be > 0"}
404{"error":"session not found"}
403INVALID_SPIN_CREDENTIAL: missing, expired, wrong-session, or already-consumed credential.
409SPIN_IN_PROGRESS: another spin for this player is still running.
429SPIN_TOO_FAST, SPIN_RATE_LIMITED, or AUTOMATION_BLOCKED. Protection errors include retryAfterMs; positive delays also set Retry-After in seconds.
402INSUFFICIENT_FUNDS, or DEMO_SESSION_UNAVAILABLE_OR_INSUFFICIENT_FUNDS if the atomic demo debit fails.
401, 402, 403, 404, 429Integrator bet rejection, returned with its code/message; fallback code is BET_REJECTED.
502BET_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.

FieldDescription
amountInteger cents to debit (bet), credit (win), or refund.
currency, player, game, tokenValues from the real session.
dateUnix timestamp in milliseconds, serialized as text.
gameIdRound number starting at 1, not the game identifier.
idProvider transaction ID. Wins have a new ID; refunds reuse the original bet ID.
nodeIdConfigured node ID, not the launch query value.
betSessionId, betTransactionIdWin only: original session token and bet provider transaction ID.
signSignature of all non-null request parameters except sign.

Example form (replace SIGNATURE with a computed signature):

amount=200&currency=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

  1. Exclude the sign field (case-insensitive) and fields whose values are null.
  2. Sort parameter names using ordinal, case-sensitive order.
  3. Concatenate the raw, decoded values only, without names or separators. Include empty-string values.
  4. Append the shared salt, encode as UTF-8, and calculate SHA-1. Output lowercase hexadecimal.
  5. 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:

VariablePurpose
Integrator__BaseUrlGameServer callback base URL, e.g. http://host.docker.internal:8080/gameserverAPI in the supplied Compose setup.
Integrator__NodeIdMerchant/node identifier. Compose uses 1.
Integrator__SaltShared signing secret. Replace the development value before deployment.
Integrator__TimeoutSecondsHTTP timeout; default 15 seconds.
Integrator__ValidateIncomingSignatureDefault false. Verifies supplied real-launch signatures only; does not require a signature.
Database__PathSQLite storage path; Compose uses /data/gameserver.db on a persistent volume.
Game__DemoBalanceCentsDummy balance per demo launch, default 100000 cents; negative values are clamped to zero.
Game__DefaultCurrencyDemo and fallback currency; default EUR.
Game__DefaultGameDemo and fallback game ID; default goldrush.
SpinProtection__MinimumIntervalMsMinimum time between admitted spins per player; default 1500 milliseconds.
SpinProtection__MaxSpinsPerMinuteMaximum admitted spins per player in a fixed 60-second window; default 30.
SpinProtection__ViolationThresholdCredential, concurrency, or timing violations in a fixed minute before blocking; default 10.
SpinProtection__BlockSecondsTemporary 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.