Keno Game Responses
This guide explains how to read the scenario field returned by placeBet for a keno game, and how to use the loadConfig data to display the pay table.
Overview
Keno is a lottery-style game. The player picks 1–10 numbers from a grid of 40, the house draws random numbers, and the player is paid based on how many of their picks match the draw. Each combination of pick count and risk level (low/medium/high) has a different pay table. The grid size and draw count are configurable in the builder.
Keno is a single-result game — each placeBet returns a complete result. There are no multi-step wager chains.
Sending Player Numbers
Every keno round requires a featureToBuy (pick count + risk) — see Pick Count & Risk Selection via Buy Features. The player's chosen numbers are sent via additionalData; if omitted, the engine auto-picks random numbers.
// Player selects their own numbers
await placeBet({
backendURL,
token,
stake: 100,
featureToBuy: 'picks_5_risk_medium',
additionalData: { playerNumbers: [3, 7, 12, 25, 38] },
});
// Auto-pick — engine chooses the numbers
await placeBet({
backendURL,
token,
stake: 100,
featureToBuy: 'picks_5_risk_medium',
});The number of playerNumbers should match the pick count for the selected feature. If the count doesn't match, the engine auto-picks instead.
The Keno Scenario
The engine enriches each placeBet response with the full draw result:
interface IKenoScenario {
picks: number; // How many numbers the player selected
risk: string; // Risk level: "low" | "medium" | "high"
matches: number; // How many player picks matched the draw
multiplier: number; // Payout multiplier for this outcome
playerNumbers: number[]; // Player's chosen numbers (1-indexed, sorted)
drawnNumbers: number[]; // House-drawn numbers (1-indexed, sorted)
gridSize: number; // Total numbers on the grid
drawCount: number; // How many numbers the house draws
}Reading the Result
const response = await placeBet({
backendURL, token, stake: 100,
featureToBuy: 'picks_5_risk_medium',
additionalData: { playerNumbers: [3, 7, 12, 25, 38] },
});
if (response.success) {
const scenario = response.result.result.scenario as IKenoScenario;
console.log('Your numbers:', scenario.playerNumbers); // [3, 7, 12, 25, 38]
console.log('House drew:', scenario.drawnNumbers); // [2, 7, 15, 19, 25, 28, 31, 33, 37, 40]
console.log(`${scenario.matches} matches, ${scenario.multiplier}x`);
// Highlight matches
const drawnSet = new Set(scenario.drawnNumbers);
for (const num of scenario.playerNumbers) {
if (drawnSet.has(num)) {
console.log(`${num} — MATCH`);
}
}
}Tile States for Rendering
Each number on the grid falls into one of four states:
| Player picked? | House drew? | State | Display |
|---|---|---|---|
| Yes | Yes | Match | Green highlight |
| Yes | No | Miss | Blue (player pick) |
| No | Yes | Drawn | Red highlight |
| No | No | Neutral | Default |
Pay Table from loadConfig
The loadConfig response includes a payTable object containing multiplier arrays for every pick count and risk level:
const config = configResponse.result.config;
const payTable = config.payTable as Record<string, Record<string, number[]>>;
// payTable["3"]["low"] → [0, 1.5, 2, 4.5]
// payTable["7"]["medium"] → [0, 0, 0, 3, 6.5, 17, 75, 530]
config.gameType; // 'keno'
config.gridSize; // 40
config.drawCount; // 10
config.minPicks; // 1
config.maxPicks; // 10
config.risks; // ["low", "medium", "high"]Each array has picks + 1 entries — index 0 is the payout for 0 matches, index 1 for 1 match, etc. An entry of 0 means no payout for that match count.
Pick Count & Risk Selection via Buy Features
Every combination of pick count and risk level is a buy feature — there is no default basegame. Every placeBet must include a featureToBuy; a call without one will be rejected with PARAMETERMISSING ("This game requires a buy feature. Provide featureToBuy.").
import { placeBet } from '@hizi.io/engine-sdk';
// Player selects 7 picks, medium risk, chooses their numbers
const featureId = `picks_${selectedPicks}_risk_${selectedRisk}`;
const response = await placeBet({
backendURL,
token,
stake,
featureToBuy: featureId,
additionalData: { playerNumbers: [2, 8, 15, 22, 29, 33, 40] },
});Single-Result Game
Keno is a single-result game — engineData.inProgress is false after every result. No continuation calls or collect calls are needed.
const response = await placeBet({ backendURL, token, stake, featureToBuy: 'picks_2_risk_low', additionalData: { playerNumbers: [5, 10] } });
if (response.success) {
const { scenario, engineData, totalWin } = response.result.result;
// engineData.inProgress === false — round is complete
}How the Math Works
Keno uses the hypergeometric distribution to compute match probabilities. The probability of exactly h matches when the player picks k numbers from a grid of N and the house draws D is:
P(h | k, N, D) = C(k, h) × C(N − k, D − h) / C(N, D)Where C(n, r) is the binomial coefficient ("n choose r").
The risk level controls the payout shape:
- Low risk — more frequent small wins; payouts start from fewer matches
- Medium risk — balanced; moderate threshold
- High risk — rare but large wins; only the top match counts pay
Multipliers are auto-computed from the target RTP and risk exponents. When "clean multipliers" is enabled in the builder, values are snapped to round numbers (e.g. 1.5x, 5x, 75x) and entry weights are adjusted to maintain the target RTP exactly.
Provably Fair Verification
Keno is single-step: the PF chain commits to a single weighted entry draw that fixes the match count. The cosmetic playerNumbers (when auto-picked) and drawnNumbers arrays are seeded from the PF commitment itself — (serverSeedHash, clientSeed) — so the engine and pfVerify reproduce the same values without any extra inputs.
Send the player picks if they chose their own
If the player typed their own playerNumbers, pass that array back to the verify call — otherwise the verifier will auto-pick from the seed and the array won't line up with the live scenario.
Request
import { pfVerify } from '@hizi.io/engine-sdk';
const reply = await pfVerify({
backendURL,
token,
serverSeed: pf.revealedServerSeed,
clientSeed: pf.clientSeed,
stake: 100,
featureToBuy: 'picks_10_high', // the round's `picks_N_risk_X` package
playerNumbers: [3, 11, 17, 22, 29, 31, 38, 40, 41, 44], // omit for auto-pick
});Reading the response
Keno is one-shot — steps has one entry:
rngData— oneintrow (the entry weighted draw). Array-equal against the livepf.rngData.steps[0].scenario.matches— equals the live match count.steps[0].scenario.playerNumbers/drawnNumbers— element-wise match against the live scenario.steps[0].totalWin— equals the live multiplier.
if (!reply.success) return;
const { rngData, steps } = reply.result;
const matchOk = deepEqual(rngData, pf.rngData);
const drawOk = deepEqual(steps[0].scenario.drawnNumbers, liveScenario.drawnNumbers);
const winOk = steps[0].totalWin === liveResult.totalWin;Next Steps
- Response Handling —
IGameResultstructure andengineDatafields. - Plinko Responses — Another single-result game with risk levels.
- Buy Features — How buy features work.