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).

  1. 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.
  2. Your page opens the iframe:
https://<platform>/play/game/{gameId}/play.html
    ?token={one-time token}
    &operator={your merchant code}
    &currency=USD&lang=en&mode=real
  1. The game client connects over WSS and presents the token. The platform creates a session and calls POST /wallet/login on your backend with that token.
  2. Your wallet validates and consumes the token (it must not work twice) and returns the player: playerId, nickname, currency, balanceMinor.
  3. The player is in the round. Bets debit your wallet synchronously; cashouts credit it.
Query paramMeaning
tokenOne-time launch token from your backend. Consumed by your /wallet/login.
operatorYour merchant code, assigned at onboarding.
currencyPlayer's wallet currency. Routes the player to a room of that currency (see Currencies). The value returned by your login is authoritative.
langUI language hint.
modereal or demo (fun money, no wallet calls — see Demo mode).
Reconnects don't need a new token. After login the client holds a session token; a dropped connection resumes the same session and bets. A second connection with the same session closes the first.

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:

  • requestURI is 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

EndpointWhenRequest (essentials)Response
/wallet/loginPlayer enters a game {token, sessionId, gameId, requestedCurrency} {code:"OK", playerId, nickname, currency, balanceMinor}
/wallet/balanceBalance refresh {playerId, currency, sessionId} {code:"OK", balanceMinor}
/wallet/betBet placed (debit) {transactionId, kind:"BET", sessionId, playerId, gameId, roundId, betId, amountMinor, currency, timestamp} {code:"OK", merchantTxId, oldBalanceMinor, balanceMinor}
/wallet/winCashout (credit) {transactionId, kind:"WIN", refTransactionId, …, amountMinor, meta:{cashoutX100}} same shape as bet
/wallet/refundRound voided / bet reversal {transactionId, kind:"REFUND", refTransactionId, …, reason} same shape as bet; unknown ref → {code:"TX_NOT_FOUND"}
/wallet/logoutSession ends {playerId, sessionId, reason} {code:"OK"}
/wallet/realitycheckOptional, responsible gaming session play stats {action:"CONTINUE"|"STOP"}

Business result codes

Always HTTP 200 with a code:

CodeMeaning
OKApplied.
INVALID_TOKENLaunch token unknown, expired or already used.
INSUFFICIENT_FUNDSDebit refused; the player sees the same.
PLAYER_BLOCKEDPlayer may not play.
BET_LIMIT_EXCEEDEDYour own limit policy refused the bet.
DUPLICATE_TRANSACTIONThis transactionId was already applied — return it with the original result fields; the platform treats it as success.
TX_NOT_FOUNDRefund for an unknown transaction. Record a tombstone: if the original bet arrives later, refuse it.
CURRENCY_MISMATCHCurrency doesn't match the player's wallet.
INVALID_SIGNATURESignature verification failed.
INTERNAL_ERRORTerminal 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 transactionId until your wallet gives a logical answer. Credits are never lost to a transient outage.
Key your ledger by 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.

EndpointBodyEffect
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.

DirectionTypePayload (essentials)
C → Shello{token, operator, currency?, mode?} — login or reconnect
C → Sbet{slot, amountMinor, autoX100} (autoX100:0 = no auto-cashout)
C → Scashout{slot}
S → Cwelcomesession token, game config & limits, wallet snapshot, room phase, table, history, fairness commit
S → Cphasephase change; betting carries the fairness commit prevHash, crashed carries the reveal
S → Ctick{mX100} — authoritative multiplier, every 100 ms
S → Cbets_deltapublic table changes, batched per tick
S → Cyou{balanceMinor, bets:[…]} — personal state, sent immediately
S → Cerror{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:

  1. betting (4–7 s) — bets accepted, fairness commit for the round is already public.
  2. running — the multiplier climbs from ×1.00 along e^(k·t); cashouts execute against the authoritative tick.
  3. crashed (2.5–3.6 s) — the crash point and the fairness reveal are shown; losing bets settle.
  4. 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 m is r, 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.

Integer discipline. Money is integer minor units; multipliers are integer hundredths. There is no floating point in any money computation — the crash point itself is computed in exact integer arithmetic (BigInt).

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.

FeatureFlagBehavior
Multi-slotbetSlots: 2Two independent bets per round, each with its own auto-cashout and cashout.
Auto-betautoBet: trueRepeats a base bet every round with stop conditions (rounds count, loss cap, win cap). Runs server-side: keeps working across reconnects.
Auto cashoutautoCashout: truePer-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 roundbetQueue: trueA bet placed mid-flight is queued and placed the moment the next betting window opens.
Turbo roomsper-room timingsSame engine, shortened phases (e.g. a 4 s betting window) — a manifest change, not a code change.
Free betsfreebets: trueVoucher-funded stakes (see Promo API); wins pay to the player's real wallet.

Cashout features

FeatureFlagBehavior
Partial cashoutpartialCashout: trueCash out a fraction of the position at the current multiplier; the remainder keeps flying with its own auto target.
Bet insuranceinsurance: {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 betssideBet: trueOver/under wagers on the round's crash point, settled from the same fair outcome.
Math changes are simulator-gated. Any feature that touches expected value (insurance pricing, side-bet odds, RTP changes) ships only after the simulator has replayed it across millions of rounds and the resulting report is inside tolerance.

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.

  1. Commit — before betting opens, the client receives prevHash: the sha256 of the hash that will decide this round.
  2. Reveal — when the round busts, the round hash is revealed. Check: sha256(hash) == prevHash.
  3. 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 login response, 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.

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.

  • Chat + reactions — per-room, rate-limited, mutable by moderators.
  • Leaderboards — daily / weekly / all-time, aggregated after settlement commits.
  • Tournaments & missions — scheduled competitions and achievement tracks computed from settled rounds only.
  • Big-win ticker — room-wide announcements above a configurable win threshold.

Responsible gaming

  • Reality check — on a configurable cadence (default hourly) the platform calls your /wallet/realitycheck with session play stats; answering STOP blocks 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

  1. Onboard — receive your merchant code, HMAC key (with key id) and staging environment; register your wallet base URL and site origin.
  2. Implement the wallet — six endpoints, signature verification, idempotency by transactionId, tombstones for unknown refunds.
  3. Implement launch — one-time tokens with a short TTL, consumed on login.
  4. Pass acceptance — the integration test suite exercises: full bet→cashout→win cycle, a losing round, INSUFFICIENT_FUNDS, duplicate win replay, refund of an unknown transaction, signature and replay rejection, and a to-the-cent ledger reconciliation.
  5. Configure games — pick skins, currencies, limits, RTP and feature flags per game; review the simulator report for your configs.
  6. 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.