Crash Game Responses
This guide explains how to drive a crash round end-to-end: opening the round with placeBet, listening to the websocket curve, and cashing out via collect.
Overview
Crash is a rules-based, real-time game. The engine commits a bust multiplier up front (kept secret server-side) and ticks a curve out over the websocket. The player's job is to cash out before the curve reaches the bust point.
The lifecycle uses three transports:
- HTTP/WS request —
placeBetopens the round and debits the stake. AplaceBetmade with a token that already references an open round resumes it instead (see Reconnecting Mid-Round). - Server-pushed events —
roundStarted→ repeatedroundTick→betCashedOut→roundEnded+balanceUpdated. On reconnect, the engine emitsroundResumedin place ofroundStarted. - HTTP/WS request —
collectcashes out one or more bets at the live multiplier (only needed for manual cashout).
A round can carry multiple bets; each bet has its own hash and may use either auto-cashout or manual cashout.
Opening a Round
Send one bet per simultaneous wager. A bet that includes autoCashOutMultiplier settles automatically when the curve reaches that target; a bet that omits it must be cashed out via collect.
import { placeBet, crashPlaceBetData } from '@hizi.io/engine-sdk';
const response = await placeBet({
backendURL,
token,
additionalData: crashPlaceBetData([
{ stake: 100, autoCashOutMultiplier: 2.5 }, // auto-cashout at 2.5x
{ stake: 100 }, // manual cashout
]),
});The total debit equals the sum of all stakes. The engine returns immediately — the round runs over the websocket from this point.
For a single manual-cashout bet you may pass stake at the top level instead of using crashPlaceBetData:
await placeBet({ backendURL, token, stake: 100 });Listening to the Curve
You must enable the websocket connection (via enableWebSockets) before calling placeBet — the curve is only delivered as server-pushed events. Since SDK 0.2.3 you don't have to wait for the handshake: a placeBet issued while the socket is still connecting is queued by the SDK and sent over the websocket once it opens (on older SDK versions such a call silently fell back to HTTP, leaving the round running with no curve events — always await enableWebSockets() there). Each event arrives as a JSON message with an event name and a payload.
import { CRASH_EVENTS, type TCrashEvent } from '@hizi.io/engine-sdk';
socket.addEventListener('message', e => {
const data = JSON.parse(e.data);
if (!data.event) return; // request/response messages carry requestId, not event
const msg = data as TCrashEvent;
switch (msg.event) {
case CRASH_EVENTS.ROUND_STARTED:
// payload.betHashes — the engine-assigned hash for each bet, in submission order.
// payload.roundHash — round id (also returned as gameRound on the placeBet reply).
break;
case CRASH_EVENTS.ROUND_RESUMED:
// Sent in place of roundStarted when placeBet was a reconnect.
// payload.currentMultiplier — live curve position at the moment of resume.
// payload.betsSubmitted — full bet list with multiplierCollected / amountCollected
// populated for any bet that already settled while disconnected.
// payload.totalWinValue — cumulative winnings on this round so far.
// Render the curve at payload.currentMultiplier and reconcile bet state, then
// continue handling roundTick / betCashedOut / roundEnded as normal.
break;
case CRASH_EVENTS.ROUND_TICK:
renderCurve(msg.payload.currentMultiplier);
break;
case CRASH_EVENTS.BET_CASHED_OUT:
// One or more bets just settled (auto or manual).
// payload.isAuto distinguishes auto-cashout from /collect.
// payload.winValues[i] corresponds to payload.betHashes[i].
break;
case CRASH_EVENTS.ROUND_ENDED:
// Curve reached the committed bust point. Round is closing.
break;
case CRASH_EVENTS.BALANCE_UPDATED:
// Final per-bet credits and total winnings. Sent immediately after roundEnded.
break;
}
});roundTick ticks at loadConfig.timerIntervalMs (default 90 ms). betCashedOut may fire one or more times per round depending on how many bets cashed out and when.
Manual Cashout
Take the bet hash from the roundStarted event's payload.betHashes (or the placeBet reply's scenario) and call collect while the curve is still climbing.
import { collect, crashCollectData } from '@hizi.io/engine-sdk';
await collect({
backendURL,
token,
additionalData: crashCollectData(betHash),
});betHash accepts a single hash or an array — pass an array to cash out multiple bets in one call. The engine pays each bet at min(liveMultiplier, maxMultiplier), so a late-arriving cashout against a curve that has already busted is rejected with GAMEROUNDNOTACTIVE.
You will see the result of a successful collect as a betCashedOut event (with isAuto: false).
Reconnecting Mid-Round
A round runs on the server independently of the client connection. If the player reloads the page or the websocket drops, the curve keeps ticking — the engine just stops sending broadcasts to a connection that isn't listening. To re-join, call placeBet again with the same token (the one carrying the open gameRound). A dropped SDK websocket reopens automatically on that call (SDK ≥ 0.2.3), so the resume both reconnects the socket and re-joins the round:
import { placeBet } from '@hizi.io/engine-sdk';
const response = await placeBet({ backendURL, token });The request body's bets are ignored on resume — the round is already opened and debited. The engine instead returns the existing scenario in the reply and broadcasts a single roundResumed event with the live multiplier, the bet list (with cashout info for any bet that already settled), and the running totalWinValue. Normal roundTick / betCashedOut / roundEnded events follow.
interface ICrashRoundResumedPayload {
/** Decimal live multiplier at the moment of resume. */
currentMultiplier: number;
/** ISO-8601 timestamp of when the curve started ticking. */
startTime: string;
/** Engine round id. */
roundHash: string;
/**
* Bets opened on the original placeBet, with collected /
* multiplierCollected / amountCollected populated for any that
* have already settled.
*/
betsSubmitted: ICrashBet[];
/** Current total winnings on this round (minor units). */
totalWinValue: number;
}The committed bust multiplier is intentionally absent — same isolation as roundStarted and roundTick.
Failure modes the resume path surfaces back to placeBet:
| Status | Returncode | Meaning |
|---|---|---|
| 400 | GAMEROUNDALREADYSTARTED | The round was already settled — clear the local round state and call placeBet again with bets. The reply may follow a roundEnded + balanceUpdated broadcast pair if the resume itself drove the stale round to settlement. |
| 400 | GAMEROUNDNOTACTIVE | Round is no longer reachable (server-side crash secret missing). Treat as already-settled. |
| 500 | DATASTRUCTUREWRONG | Open round exists but its game state is malformed — operator intervention required. |
Scenario Shape
The placeBet response embeds an ICrashScenario on the round-opening game state entry:
interface ICrashScenario {
/** ISO-8601 timestamp marking when the curve started ticking. */
startTime: string;
/** Bets posted on placeBet, in submission order. */
betsSubmitted: ICrashBet[];
/** Cashouts keyed by bet hash, populated as bets settle. */
betsCollected?: Record<string, { multiplier: number; amount: number }>;
}
interface ICrashBet {
/** Engine-assigned hash. Use this to cash out via crashCollectData. */
hash: string;
stake: number;
autoCashOutMultiplier?: number;
multiplierCollected?: number;
amountCollected?: number;
collected?: boolean;
inProgress?: boolean;
collectedAtinMS?: number;
}The committed bust multiplier is never returned to the client. The only way to learn where the curve crashed is to wait for the roundEnded event's payload.currentMultiplier.
Configuration
The creator-built loadConfig block is surfaced through loadConfig().result.config.loadConfig:
import type { ICrashLoadConfig } from '@hizi.io/engine-sdk';
const cfg = configResponse.result.config.loadConfig as ICrashLoadConfig;
// cfg.startMultiplier, cfg.growth, cfg.timerIntervalMs, cfg.startGameDelayMs,
// cfg.gameRtp, cfg.minMultiplier, cfg.maxMultiplier,
// cfg.postCashoutSpeedMultiplierFrontends typically use startMultiplier and growth to render their own client-side preview curve between server ticks, and maxMultiplier to render the payout cap.
Game Flow
placeBet (with bets[])
└─ HTTP/WS reply: SUCCESS, round opened, balance debited
└─ event: roundStarted { betHashes, roundHash }
└─ event: roundTick { currentMultiplier } (every timerIntervalMs)
│
├─ auto-cashout target reached
│ └─ event: betCashedOut { isAuto: true, ... }
│
├─ client calls collect(crashCollectData(hash))
│ └─ event: betCashedOut { isAuto: false, ... }
│
├─ client reconnects with the same token → placeBet (no body bets)
│ └─ event: roundResumed { currentMultiplier, betsSubmitted, totalWinValue, ... }
│ └─ tick / cashout flow resumes
│
└─ curve reaches committed bust point
├─ event: roundEnded { currentMultiplier }
└─ event: balanceUpdated { totalWinValue, winValues }A crash round runs entirely on the server; the client connection is just a viewport into it. placeBet opens a new round when called without an active gameRound on the token, and resumes the existing round when called with one — there is no other continuation flow.
Complete Example
import {
placeBet,
collect,
crashPlaceBetData,
crashCollectData,
CRASH_EVENTS,
type TCrashEvent,
} from '@hizi.io/engine-sdk';
async function playCrash(socket: WebSocket, target: number) {
let manualHash: string | null = null;
socket.addEventListener('message', e => {
const data = JSON.parse(e.data);
if (!data.event) return;
const msg = data as TCrashEvent;
if (msg.event === CRASH_EVENTS.ROUND_STARTED) {
// Two bets submitted: index 0 is auto, index 1 is manual.
manualHash = msg.payload.betHashes[1];
} else if (msg.event === CRASH_EVENTS.ROUND_TICK) {
renderCurve(msg.payload.currentMultiplier);
// Manual cashout the moment we cross our chosen target.
if (manualHash && msg.payload.currentMultiplier >= target) {
const hash = manualHash;
manualHash = null;
collect({ backendURL, token, additionalData: crashCollectData(hash) });
}
} else if (msg.event === CRASH_EVENTS.BET_CASHED_OUT) {
console.log(`cashed out at ${msg.payload.multiplier}x (auto=${msg.payload.isAuto})`);
} else if (msg.event === CRASH_EVENTS.ROUND_ENDED) {
console.log(`round ended at ${msg.payload.currentMultiplier}x`);
}
});
await placeBet({
backendURL,
token,
additionalData: crashPlaceBetData([
{ stake: 100, autoCashOutMultiplier: 2.0 },
{ stake: 100 },
]),
});
}Provably Fair Verification
Crash's seed-derived surface is the committed maxMultiplier — the curve's bust point. Everything else (the curve interpolation, the player's manual cashout time, auto-cashout settlement) is wall-clock or operator-audited, not derivable from the seeds. Use pfVerify to confirm the bust point was fixed before the round started.
Cashouts are not PF-derived
A passing pfVerify proves the engine couldn't have moved the bust point after committing to it. It does not verify your bet was settled at the right multiplier — that's a separate operator-audit concern (timestamps, multiplier-at-cashout reconciliation).
Request
No actions, no bets needed — the round's maxMultiplier is a function of the seeds alone.
import { pfVerify } from '@hizi.io/engine-sdk';
const reply = await pfVerify({
backendURL,
token,
serverSeed: revealedServerSeed, // from the ROUND_ENDED broadcast
clientSeed: pf.clientSeed,
});Reading the response
One-shot — steps has one entry.
rngData— onedoublerow (the entropy that derives the multiplier). Array-equal against the livepf.rngData.steps[0].scenario.maxMultiplier— equals the multiplier the curve crashed at on the liveROUND_ENDEDevent.
if (!reply.success) return;
const { rngData, steps } = reply.result;
const rngOk = deepEqual(rngData, pf.rngData);
const bustOk = steps[0].scenario.maxMultiplier === liveCrashPoint;If both line up the bust point was committed to before any bets were placed.
Next Steps
- Response Handling —
IGameResultstructure andengineDatafields. - Blackjack Responses — A rules-based game with a richer action tree.
- Types Reference —
ICrashScenario,ICrashBet,ICrashLoadConfig,ICrashRoundResumedPayload,TCrashEvent,CRASH_EVENTS,crashPlaceBetData,crashCollectData.