Documentation
Integrate the platform
Everything needed to take a crash game live on your casino: the launch
flow, the seamless-wallet API you implement, the protocol the game speaks, the math it runs,
and how players verify it. Money amounts are integer minor units everywhere;
multipliers are integer hundredths (mX100, so 231 = ×2.31).
Overview
Snogi is a provider-side platform: we host and operate the games, you embed them. The integration model is a seamless wallet — player money always lives in your systems. The platform never holds balances; it asks your wallet to debit bets and credit wins over a signed server-to-server API and keeps a full ledger of every operation for reconciliation.
- You build: a launch endpoint on your site and six wallet endpoints on your backend.
- We run: game clients, authoritative game servers, math, fairness chains, settlement, promo, admin.
- The server is authoritative for everything: the multiplier, the crash point, cashout execution and settlement. The client only renders and interpolates between ticks.
Architecture
The platform is layered by money authority:
- WS gateways — stateless WebSocket termination, auth, rate limiting, fan-out. Any number of nodes behind a balancer; killing one only reconnects its sockets.
- Room workers — the authoritative game loop: phase machine, ticks, auto-cashouts, crash, settlement, wallet calls. One room is owned by exactly one worker at a time (a distributed lease guarantees it), so there is no consensus inside a round.
- PostgreSQL — ledger, rounds, bets, sessions, fairness chains. Round settlement (lost bets + totals) commits in a single transaction.
- Redis — pub/sub between gateways and workers, sessions, presence.
Games are folders on top of one engine: a manifest (math, phase timings, feature flags, per-currency limits), a theme, and a scene. Adding a skin adds zero engine code.
Launch flow
Games launch in an iframe with a one-time token your backend issues (Mode B: you own the player session).
- The player opens a game on your site. Your backend generates a one-time launch token (opaque string, short TTL — 5 minutes is typical) bound to the player.
- Your page opens the iframe:
https://<platform>/play/game/{gameId}/play.html
?token={one-time token}
&operator={your merchant code}
¤cy=USD&lang=en&mode=real
- The game client connects over WSS and presents the token. The platform creates a
session and calls
POST /wallet/loginon your backend with that token. - Your wallet validates and consumes the token (it must not work twice)
and returns the player:
playerId,nickname,currency,balanceMinor. - The player is in the round. Bets debit your wallet synchronously; cashouts credit it.
| Query param | Meaning |
|---|---|
token | One-time launch token from your backend. Consumed by your /wallet/login. |
operator | Your merchant code, assigned at onboarding. |
currency | Player's wallet currency. Routes the player to a room of that currency (see Currencies). The value returned by your login is authoritative. |
lang | UI language hint. |
mode | real or demo (fun money, no wallet calls — see Demo mode). |
Wallet API
You expose six HTTPS endpoints (plus one optional). All are POST with JSON
bodies. The platform signs every request; you verify the signature, then answer with
HTTP 200 and a business code — transport errors (5xx, timeouts) are what trigger retries.
Request signing
Every request carries five headers:
X-GP-Merchant: acme // your merchant code
X-GP-Key: k1 // key id — supports zero-downtime rotation
X-GP-Timestamp: 1760000000 // unix seconds
X-GP-Nonce: 7f9caf… // uuid, unique per request
X-GP-Signature: hex( HMAC-SHA256( secret,
ts + "." + nonce + "." + requestURI + "." + rawBody ) )
Verification rules on your side:
requestURIis the path including query (e.g./wallet/bet). Sign the raw body bytes — never re-serialize the JSON before verifying.- Reject when
|now − ts| > 30 s; reject a nonce seen within the last 10 minutes (replay window). - Compare signatures constant-time.
- Multiple key ids can be live at once; verify against the key named by
X-GP-Key. This is how keys rotate with zero downtime.
Endpoints
| Endpoint | When | Request (essentials) | Response |
|---|---|---|---|
/wallet/login | Player enters a game | {token, sessionId, gameId, requestedCurrency} |
{code:"OK", playerId, nickname, currency, balanceMinor} |
/wallet/balance | Balance refresh | {playerId, currency, sessionId} |
{code:"OK", balanceMinor} |
/wallet/bet | Bet placed (debit) | {transactionId, kind:"BET", sessionId, playerId, gameId, roundId, betId, amountMinor, currency, timestamp} |
{code:"OK", merchantTxId, oldBalanceMinor, balanceMinor} |
/wallet/win | Cashout (credit) | {transactionId, kind:"WIN", refTransactionId, …, amountMinor, meta:{cashoutX100}} |
same shape as bet |
/wallet/refund | Round voided / bet reversal | {transactionId, kind:"REFUND", refTransactionId, …, reason} |
same shape as bet; unknown ref → {code:"TX_NOT_FOUND"} |
/wallet/logout | Session ends | {playerId, sessionId, reason} |
{code:"OK"} |
/wallet/realitycheck | Optional, responsible gaming | session play stats | {action:"CONTINUE"|"STOP"} |
Business result codes
Always HTTP 200 with a code:
| Code | Meaning |
|---|---|
OK | Applied. |
INVALID_TOKEN | Launch token unknown, expired or already used. |
INSUFFICIENT_FUNDS | Debit refused; the player sees the same. |
PLAYER_BLOCKED | Player may not play. |
BET_LIMIT_EXCEEDED | Your own limit policy refused the bet. |
DUPLICATE_TRANSACTION | This transactionId was already applied — return it with the original result fields; the platform treats it as success. |
TX_NOT_FOUND | Refund for an unknown transaction. Record a tombstone: if the original bet arrives later, refuse it. |
CURRENCY_MISMATCH | Currency doesn't match the player's wallet. |
INVALID_SIGNATURE | Signature verification failed. |
INTERNAL_ERROR | Terminal failure; the platform will not blind-retry a bet on this. |
Idempotency & retries
The invariant both sides defend: a transaction id is applied at most once, and a duplicate always returns the original result instead of double-applying.
- login / balance — 3 s timeout, no retries (the player just retries the action).
- bet — 1 s timeout, up to 3 attempts with the same
transactionId. If still unresolved the player's bet is refused and a refund probe reconciles whether the debit landed. - win / refund — queued in a durable outbox, retried with backoff
(1 s → 5 s → 30 s → 2 m) with the same
transactionIduntil your wallet gives a logical answer. Credits are never lost to a transient outage.
transactionId. Retries
re-send the same id; DUPLICATE_TRANSACTION + original fields is the correct
answer to a replay, and it's what reconciliation expects.On the platform side every operation is a ledger row with a state machine
(pending → confirmed / failed); round settlement (lost bets + round totals)
commits in one database transaction, and leaderboards/tournaments aggregate strictly
after that commit — an engagement feature can never roll back money.
Promo API
Promo actions are merchant-initiated calls in the opposite direction — your backend calls the platform, signed with the same HMAC scheme (your key, your merchant code). Issuance is idempotent by request nonce, and every merchant has hard promo ceilings enforced before anything is granted.
| Endpoint | Body | Effect |
|---|---|---|
POST /v1/promo/freebet |
{playerId, count, amountMinor, currency, expiresAt} |
Grants free-bet vouchers; stakes are platform-funded, wins credit the player through the normal win flow (kind:"FREEBET" marks the stake). |
POST /v1/promo/rain |
{game, room, totalMinor, recipients} |
Splits a drop across live players in a room, announced in the room feed. |
The same executor (with the same limits and audit trail) backs the admin panel's promo tools — there is no privileged path that mints money outside it.
Demo mode
Launching with mode=demo opens a fun-money session: the
platform never calls your wallet (the launch token stays unused), the player gets a
configurable fun balance (default 1,000.00), and the balance refills automatically at the
start of a betting window whenever it can no longer cover the minimum bet.
- The client shows a persistent DEMO badge.
- Demo bets are isolated: they never appear on the public table, in tickers or leaderboards.
- Demo sessions attach only to shared tables, never to a brand's private table.
This is exactly what the playable demos on this site use.
WebSocket protocol
You normally don't implement this — our game clients speak it — but it's documented for
review and for custom clients. Every server message is an envelope
{v:1, t, seq?, now, d}; client messages are {v:1, t, msgId?, d}.
Broadcast messages carry a per-room monotonic seq — a gap makes the client
resynchronize with a full snapshot.
| Direction | Type | Payload (essentials) |
|---|---|---|
| C → S | hello | {token, operator, currency?, mode?} — login or reconnect |
| C → S | bet | {slot, amountMinor, autoX100} (autoX100:0 = no auto-cashout) |
| C → S | cashout | {slot} |
| S → C | welcome | session token, game config & limits, wallet snapshot, room phase, table, history, fairness commit |
| S → C | phase | phase change; betting carries the fairness commit prevHash, crashed carries the reveal |
| S → C | tick | {mX100} — authoritative multiplier, every 100 ms |
| S → C | bets_delta | public table changes, batched per tick |
| S → C | you | {balanceMinor, bets:[…]} — personal state, sent immediately |
| S → C | error | {code, message, reMsgId?} |
Bet/cashout rejections use stable codes:
BETTING_CLOSED, BET_ALREADY_PLACED, BAD_AMOUNT,
LIMIT, INSUFFICIENT_FUNDS, TOO_LATE,
NO_ACTIVE_BET, WALLET_UNAVAILABLE, RATE_LIMITED,
SESSION_TAKEN.
Round lifecycle
Rooms cycle continuously through four phases; timings come from each game's manifest:
- betting (4–7 s) — bets accepted, fairness commit for the round is already public.
- running — the multiplier climbs from ×1.00 along
e^(k·t); cashouts execute against the authoritative tick. - crashed (2.5–3.6 s) — the crash point and the fairness reveal are shown; losing bets settle.
- recovery (5.5–7 s) — settlement completes, history updates, next round arms.
Execution order inside a tick is fixed and favors the player's standing instruction: auto-cashouts execute first (any target ≤ the tick's multiplier, honored at the target multiplier — even if it is reached mid-tick or at the crash instant), and only then is the crash checked. Manual cashouts are valid strictly before the crash moment at the multiplier current when received. Displayed and settled multipliers always floor to hundredths.
Crash math & RTP
The crash point derives from the round's fairness hash (bustabit v2 scheme, parameterized by RTP):
// hmac = HMAC-SHA256(salt, roundHash); r = rtp (e.g. 0.99)
X = uint52(hmac) / 252 // uniform in [0,1)
crash = max( 1.00, floor( 100·r / (1−X) ) / 100 )
crash = min( crash, maxCrash ) // cosmetic cap per game
Consequences, for RTP = 99% (rtpBp: 9900):
P(crash ≥ m) = r/m— e.g. 49.5% of rounds reach ×2.- Instant bust (×1.00) happens with probability
(1.01 − r)/1.01≈ 1.98%. - Median crash ≈ ×1.98. RTP for a player cashing out at target
misr, independent of the target.
The multiplier during flight is m(t) = floor(100·e^(k·t))/100, ticked
authoritatively every 100 ms; the client interpolates between ticks for display only. With
the flagship curve k = 0.00006, ×2 arrives at ≈ 11.5 s.
All of this is configurable per game in the manifest:
math: {curveK, rtpBp, maxCrashX100}, phase timings, feature flags, and
per-currency bet limits. The exact math functions the live engine executes also run in the
simulator — RTP reports and the RTP certificate are produced by replaying millions of rounds
through the same code, not a reimplementation.
Betting features
Feature flags live in each game's manifest; a missing flag means the feature does not exist for that game — clients hide it and servers reject it.
| Feature | Flag | Behavior |
|---|---|---|
| Multi-slot | betSlots: 2 | Two independent bets per round, each with its own auto-cashout and cashout. |
| Auto-bet | autoBet: true | Repeats a base bet every round with stop conditions (rounds count, loss cap, win cap). Runs server-side: keeps working across reconnects. |
| Auto cashout | autoCashout: true | Per-bet target (≥ ×1.01); executed by the server at the target, ahead of the crash check. Preset buttons: ×1.5 / ×2 / ×5 / ×10. |
| Bet next round | betQueue: true | A bet placed mid-flight is queued and placed the moment the next betting window opens. |
| Turbo rooms | per-room timings | Same engine, shortened phases (e.g. a 4 s betting window) — a manifest change, not a code change. |
| Free bets | freebets: true | Voucher-funded stakes (see Promo API); wins pay to the player's real wallet. |
Cashout features
| Feature | Flag | Behavior |
|---|---|---|
| Partial cashout | partialCashout: true | Cash out a fraction of the position at the current multiplier; the remainder keeps flying with its own auto target. |
| Bet insurance | insurance: {costBp, thresholdX100} | Optional premium at bet time; if the round busts below the threshold (e.g. ×1.20) the stake is refunded. Priced from the crash distribution and simulator-verified. |
| Side bets | sideBet: true | Over/under wagers on the round's crash point, settled from the same fair outcome. |
Provably fair
Each room consumes a precomputed sha256 hash chain:
chain[i] = sha256(chain[i−1]) from a random origin seed. Rounds consume the
chain backwards, so each revealed hash is verifiable against the one revealed
before it, and the whole history is pinned by the chain's terminal hash published at
provisioning.
- Commit — before betting opens, the client receives
prevHash: the sha256 of the hash that will decide this round. - Reveal — when the round busts, the round hash is revealed. Check:
sha256(hash) == prevHash. - Recompute — the crash point is
crashPoint(HMAC-SHA256(salt, hash))with the public per-room salt and the formula in Crash math.
A public verifier page (served at /play/game/_shared/fair-verify/ on the platform)
does all three checks in the browser for any past round — no account needed. Chains survive
restarts and failovers: the cursor is persisted, and a chain is never reused past its end.
Client seeds (optional)
Games can enable the Aviator-style scheme instead (features.clientSeeds):
the round outcome mixes a server seed with seeds contributed by players in the round, with
the server seed's hash committed up front. Same auditability, different trust story —
pick per game.
RTP certificate
The simulator replays the exact production math over millions of rounds and emits an RTP report per game config. Because it is the same code, not a model of it, the certificate matches live behavior by construction.
Currencies & tables
Rooms are single-currency: pots, tickers and leaderboards aggregate in one currency per table. A multi-currency brand gets one room instance per currency, and players route to their currency's room automatically at launch.
- The player's currency is decided by your
loginresponse, not by the client. A mismatch with the claimed launch currency is rejected, never silently converted. - Bet limits are per currency in the game manifest (
limits[USD] = {minBetMinor, maxBetMinor}, …). Currency decimals come from a registry (USD 2, JPY 0, BTC 8, …) — an unknown currency is rejected, never assumed. - Tables can be shared across brands or private to one brand. Private tables are invisible to other merchants' players; demo sessions attach only to shared tables.
Responsible gaming
- Reality check — on a configurable cadence (default hourly) the platform
calls your
/wallet/realitycheckwith session play stats; answeringSTOPblocks further bets in the session. - Limits — durable per-player loss/stake limits enforced at bet time.
- Self-exclusion — enforced at login for the whole exclusion term; an excluded player cannot open a session.
Reliability
- One authoritative owner per room. A worker holds a distributed lease per room and stops touching money or publishing the moment the lease is lost — split-brain is structurally prevented. On failover, the open round is voided and live bets are refunded automatically.
- Gap-free client state. Broadcasts carry a per-room monotonic sequence; a client that misses a message resynchronizes with a full snapshot instead of guessing.
- Recovery-first design. After a crash or restart, unsettled rounds are voided and refunded; the fairness chain resumes at its persisted cursor. The ledger and your wallet reconcile to the cent.
- Observability. Prometheus metrics on every money and protocol path, alert rules and incident runbooks; per-merchant dashboards in the admin panel.
- Admin panel. Platform and per-merchant views, password + mandatory TOTP, role-based access, and an append-only audit log written in the same transaction as every mutation. Admin actions cannot mint money: promo goes through the same limited executor; a ledger retry re-arms the existing transaction with the same id.
Go-live checklist
- Onboard — receive your merchant code, HMAC key (with key id) and staging environment; register your wallet base URL and site origin.
- Implement the wallet — six endpoints, signature verification, idempotency by
transactionId, tombstones for unknown refunds. - Implement launch — one-time tokens with a short TTL, consumed on login.
- Pass acceptance — the integration test suite exercises: full bet→cashout→win cycle, a losing round,
INSUFFICIENT_FUNDS, duplicatewinreplay, refund of an unknown transaction, signature and replay rejection, and a to-the-cent ledger reconciliation. - Configure games — pick skins, currencies, limits, RTP and feature flags per game; review the simulator report for your configs.
- Launch — go live on shared or private tables; monitor from the merchant dashboard.
Questions during integration go to your onboarding engineer — every merchant gets a named one. The staging wallet emulator (with fault injection: latency, timeouts, duplicate deliveries) is available from day one so you can test your wallet against realistic failure modes before certification.
Social & engagement
The social layer rides on the same room feed but is deliberately best-effort: chat, reactions, the big-win ticker and rain announcements carry no sequence numbers and can never block, delay or roll back a money operation.