GSPlay Integration API
Reference for the Champions, WolfSlot and CandySlot provider implementation. There are two interfaces: browser-to-GSPlay launch and gameplay APIs, and GSPlay-to-integrator session and wallet callbacks. The contract documented here describes the local wallet integration.
Quick Start
The default Docker GSPlay URL is http://localhost:8091. Use your public HTTPS URL for remote browsers.
All monetary values are integer cents: 100 means 1.00.
Demo Play
http://localhost:8091/launch?mode=demo&game=champions_slot&exit=%2Findex.html
CandySlot demo: /launch?mode=demo&game=candyslot.
Register catalog code candyslot with provider GSPlay and the existing GSPlay server.
Real sessions also accept candy_slot. CandySlot uses 20 paylines, bets of 1-20 credits
per active line, and the five-reel wolfSymbols/reelStops response format.
Outcomes are decided on the server; scatter/free-game/hold bonuses are disabled.
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 GSPlay'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. - GSPlay fetches the session, opens the selected game, 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 /play.html?token=... on success.
GSPlay's host page contains the Exit control and embeds the selected title; the embedded game does not draw its own host exit button.
| 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, GSPlay 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 | Return URL supplied by the casino's gameservers.exit_url field via the launch URL. Stored in the session; the GSPlay host closes the session and navigates the top window there. If omitted, the host falls back to /. |
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 previous local session for the same game and synchronizes the local balance when the integrator supplies one. GSPlay selects each game's board and payout on the server.
Browser Game API
Paths are relative to GSPlay. 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.
Automatic build compatibility
GET /api/game/version returns {"buildId":"BUILD_ID"} with
Cache-Control: no-store. The ID is automatically fingerprinted from published assets and the backend.
Entry HTML embeds this ID and versions local script/link URLs. Send the loaded HTML's build ID, not one
fetched later to impersonate a newer client: state uses the X-GSPlay-Build header; spins and
preference saves require clientVersion in JSON. A mismatch (or missing POST version)
returns 409 CLIENT_VERSION_OUTDATED and permanently closes the local session.
Stop autoplay and new spins; let an admitted round settle, then require a fresh launch.
Headerless state reads are permitted for diagnostics, but do not authorize unversioned spins.
GET /api/game/state?token=TOKEN
Use the fresh GSPlay token returned in the launch redirect for all game APIs. Wallet callbacks continue to use the original casino token. Every real launch permanently disables the player's previous launch for the same game, including when the casino token is reused. Reloading or closing the old game does not revive it or close the new launch.
{
"token": "TOKEN",
"playerName": "player123",
"currency": "EUR",
"game": "champions_slot",
"rounds": 0,
"totalBetCents": 0,
"totalWinCents": 0,
"balanceCents": 100000,
"mode": "real",
"buildId": "BUILD_ID",
"spinCredential": "SINGLE_USE_CREDENTIAL",
"retryAfterMs": 0,
"minimumIntervalMs": 1500,
"exitUrl": "/index.html"
}
Demo returns mode: "demo". Unknown sessions return 404 with
{"error":"session not found"}; closed sessions return 403 INVALID_SPIN_CREDENTIAL.
Real state calls integrator /session to validate activity:
upstream session/authentication errors (including 401/403/404) permanently close the local session and become
401 PLAYER_NOT_FOUND; other failures become 503 INTEGRATOR_UNAVAILABLE.
The returned balance is refreshed from the casino validation response when provided.
POST /api/game/spin
{"token":"TOKEN","betCents":200,"lines":1,"spinCredential":"SINGLE_USE_CREDENTIAL","clientVersion":"BUILD_ID"}
Example successful response (reels, payouts, and IDs vary):
{
"round": 1,
"reels": ["symbol-a", "symbol-b", "symbol-c"],
"championsSymbols": [[1,2,3],[4,4,4],[2,3,1]],
"winCents": 0,
"winLines": [],
"balanceCents": 99800,
"betTransactionId": 1700000000001,
"winTransactionId": null
}
GSPlay selects the payout and 3×3 numeric championsSymbols board on the server. The center row is the
active payline and symbol IDs range from 1 to 8. The frontend animates this exact board. A bet is in integer cents;
lines is 1–3; each active line must bet 1–5 credits (100 cents per credit). Demo uses the same response
shape, with both transaction IDs null. The demo and real wallet paths use the same server outcome engine.
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.
A fresh real launch replaces only the same player's session for the same game (case-insensitive game ID). Different games remain active and have independent credentials, while cooldowns and rate limits remain per player. Session/credential errors require a fresh launch, never a reload or automatic reconnect. Both bundled games stop polling, autoplay, and betting on these errors, including errors returned by preference saves. Credential error codes are recognized case-insensitively in singular and plural form. HTTP 401/403/404 remain terminal even for non-JSON responses; session rejections during wallet refunds also disable the session. Insufficient funds, rate limits, and temporary network errors remain recoverable.
| Status | Spin Error |
|---|---|
| 400 | {"error":"bet must be > 0"} |
| 404 | {"error":"session not found"} |
| 403 | INVALID_SPIN_CREDENTIAL: permanently disabled session, or missing, expired, wrong-session, or already-consumed credential. Stop spins, autoplay, and state refreshes; the session cannot recover. A new launch is required. |
| 409 | SPIN_IN_PROGRESS: another spin for this player is still running. |
| 409 | CLIENT_VERSION_OUTDATED: missing or outdated clientVersion. The local session is permanently disabled before a wallet debit. Launch again; do not reload or retry. |
| 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. |
| 409 | BET_PENDING_RESOLUTION: another bet callback is still unresolved; wait for its retry/refund before starting another spin. |
| 403 | Integrator session/authentication rejection permanently closes the local session and returns INVALID_SPIN_CREDENTIAL. |
| 402 | Insufficient casino funds, returned with its code/message; this callback is not retried. |
| 502 | BET_PENDING_RESOLUTION: casino bet result is uncertain. GSPlay retries the same bet ID, does not run a spin, and automatically queues a refund if the bet is later confirmed. Do not retry the spin. |
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 sessions return 404; closed sessions return 403 with INVALID_SPIN_CREDENTIAL.
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":"champions_slot","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. Network errors, HTTP 408, 429, and 5xx responses are persisted and retried
with exponential backoff (2 seconds up to 5 minutes) using the same operation and provider transaction ID.
The callback date and signature are refreshed on each attempt. The casino must therefore guarantee idempotency
by operation and transaction ID. Other HTTP errors are not retried. An uncertain bet is not spun later: if a
retry confirms it, GSPlay automatically queues a refund. Wins can be retried after the spin response has returned.
Review the Activity logs tab in /admin.html to inspect all callback attempts, responses, and spin events.
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 | GSPlay callback base URL, e.g. http://host.docker.internal:8080/gsplayAPI 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 champions_slot. |
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.