Events & data model
Event envelope
{
"v": 1,
"type": "round.open",
"table": "B001",
"seq": 1532,
"prev": 1531,
"ts": "2026-09-24T03:00:30.000Z",
"game": "baccarat",
"shoe": "260924-03",
"round": 12,
"roundKey": "B001-260924-03-12",
"data": {}
}| Field | Type | Required | Notes |
|---|---|---|---|
v | int | yes | Protocol major version, currently 1; higher versions get UNSUPPORTED_VERSION |
type | string | yes | Event type (see below); types the platform does not know yet are acknowledged and ignored |
table | string | yes | Source table ID, [A-Za-z0-9_.:-]{1,64} |
seq | int | yes | Strictly increasing within the stream, ≥ 1; gaps are allowed |
prev | int | yes | seq of the previous event in the stream; 0 for the first; must be less than seq |
ts | string | yes | Event time at the source, UTC ISO-8601 with milliseconds (2026-09-24T03:00:30.000Z), and a real date. Source hosts must use NTP |
game | string | for known types | baccarat, dragontiger or niuniu |
shoe | string | for shoe and round events | Source shoe ID, [A-Za-z0-9_.:-]{1,64}; never reused on the same table (include the date). Niu Niu has no shoe; this is the session (see Niu Niu) |
round | int | for round events | Round number within the shoe (for Niu Niu, within the session), 1–100000 |
roundKey | string | for round events | Unique forever on the table, [A-Za-z0-9_.:-]{1,128}; corrections and voids reuse it |
data | object | yes | Depends on the type |
- Unknown fields are ignored; adding fields is a compatible change and does not bump the major version.
- "Shoe events":
shoe.start,shoe.end. "Round events":round.open,round.close,card,reveal,round.result,round.correct,round.cancel(round events needshoetoo).
Event catalog
type | When | data |
|---|---|---|
table.info | Once per table after connecting; again when it changes | {name, game, decks?, dealer?, video?[], capabilities: {cardByCard, countdown, snapshot}} |
table.status | Status changes | {status: "open"|"paused"|"maintenance"|"closed", reason?} |
table.snapshot | When you cannot resend, or to re-align after reconnecting | {status, phase: "idle"|"betting"|"closed"|"result", shoe?, round?, roundKey?, closesAt?, history: [{round, result}], gap} |
shoe.start | A new shoe starts | {decks?} |
shoe.end | A shoe ends (optional) | {rounds} |
round.open | Betting opens | {closesAt?, betSeconds?, lastInShoe?, dealer?} |
round.close | Betting closes | {} |
card | A card is dealt (optional, needs cardByCard) | {pos, card, faceDown?} |
reveal | Niu Niu hands are turned over one by one (optional) | {hands: ["P1"]} |
round.result | The result (first version) | {rev: 1, complete, result} |
round.correct | Corrects a published result | {rev: n+1, complete, result, reason} |
round.cancel | The round is void | {reason: "misdeal"|"dealer_error"|"device_error"|"table_closed"|"other", note?} |
dealer.change | Dealer changes | {dealer: {id, name, photoUrl?}} |
What the platform does:
type | Handling |
|---|---|
table.info | Updates the table catalog; unbound source tables are listed as "discovered, not bound" |
table.status | Anything but open stops betting immediately |
table.snapshot | Rebuilds the state and the shoe's road from the snapshot |
shoe.start | Clears the road and tells players about the new shoe |
round.open | Opens the round and computes when betting closes (see Timing); lastInShoe: true makes the game show "last round of the shoe" |
round.close | Closes betting immediately |
card | Closes betting at once if still open; animates the card. For Niu Niu, a card with faceDown: true is kept secret: players are only told that a card was dealt to that position |
reveal | Shows the face-down cards of those hands (the game turns them over hand by hand); Niu Niu only |
round.result | Checked, then settled |
round.correct | Recalculated automatically or kept and marked disputed, depending on trust level and guardrails (see Trust levels) |
round.cancel | Before settlement: full refund; after settlement: same rules as round.correct |
dealer.change | Shown in the game |
Field limits: name 1–128 characters; decks 1–16; betSeconds 1–600; dealer.id 1–64 characters, dealer.name 1–128 characters, dealer.photoUrl must be https://; video at most 8 entries, each {protocol: "webrtc"|"flv"|"hls"|"agora"|"whep", url?, quality?: "sd"|"hd", auth: "none"|"token"|"referer"}; reason and note at most 500 characters; history at most 200 entries; closesAt is a UTC time with milliseconds; faceDown is a boolean; reveal's hands holds 1–4 distinct values out of B, P1, P2, P3.
More rules:
- Another
round.openfor the sameroundKeywhile betting is open updates the countdown (for example when the dealer resets the timer); after betting has closed it is ignored. - A result without a preceding
round.open(for example when a source joins mid-round) only updates the road; nobody can bet on that round. - A result cannot be changed with a second
round.result: useround.correctwith a higherrev(round.resultalways hasrev1;round.correcthasrev≥ 2 and areason). A different result with the samerevkeeps the original result and is recorded as a conflict.
Game data model
Card encoding
<rank><suit>: ranks A 2 3 4 5 6 7 8 9 10 J Q K, suits S spades, H hearts, D diamonds, C clubs. For example AS, 10H, QD.
Baccarat result
{
"cards": { "P1": "2C", "B1": "4H", "P2": "3D", "B2": "2S", "P3": "10S" },
"player": 5,
"banker": 6,
"winner": "B",
"playerPair": false,
"bankerPair": false
}- Card positions
P1 B1 P2 B2 P3 B3; undealt cards are omitted. playerandbankerare points 0–9;winnerisP,BorT(tie).- With
complete: trueall cards must be present (at leastP1 B1 P2 B2). The platform recomputes points, winner and pairs and checks the drawing rules; a mismatch yieldsRESULT_INCONSISTENT, handled according to the trust level. - With
complete: falseyou may send onlyplayer,banker,winner,playerPairandbankerPair, plus optionallycardCount(total cards on both sides, 4–6). - The example is banker wins with 6: player 2+3=5, draws a 10 and stays at 5; banker 4+2=6 and stands on the player's third card of 0.
Dragon tiger result
{ "cards": { "D": "KH", "T": "9S" }, "winner": "D" }winner:Ddragon,Ttiger,TIE.- Ranks decide: A=1 … K=13 (ace lowest), suits do not count; equal rank with equal suit is a suited tie.
- With
complete: falseyou may send onlywinner; a suited tie and big/small/odd/even (which look at each side's card) cannot be decided then, so those bets are refunded.cardevents already sent are still used. cardevents use the positionsDandT.
Niu Niu result
{
"cards": {
"F": "7H",
"B-1": "KS", "B-2": "QD", "B-3": "10C", "B-4": "3S", "B-5": "5H",
"P1-1": "4C", "P1-2": "6D", "P1-3": "JS", "P1-4": "9C", "P1-5": "KH",
"P2-1": "2D", "P2-2": "8S", "P2-3": "QC", "P2-4": "AH", "P2-5": "2C",
"P3-1": "7D", "P3-2": "3H", "P3-3": "KD", "P3-4": "5C", "P3-5": "5D"
},
"hands": { "B": 8, "P1": 9, "P2": 3, "P3": 10 },
"winners": { "P1": "P", "P2": "B", "P3": "P" }
}- Positions:
Ffirst card; bankerB-1toB-5; player 1P1-1toP1-5, player 2P2-1toP2-5, player 3P3-1toP3-5(-nis the n-th card dealt to that hand). hands: each hand's rank code,0No Bull,1–9Bull 1–9,10Niu Niu,11Five Face Bull,12Bomb,13Five Small Bull (11–13 only at tables with special hands turned on).winners:P1,P2andP3each against the banker,Pplayer wins,Bbanker wins (there are no ties). Hand and ranking rules are in Tables & round results.- With
complete: trueall 21 cards must be present (the first card plus 5 per hand);handsmay be left out. - With
complete: falseat leastwinnersis required (enough to settle equal bets). A double bet needs the winning hand's rank, so sendhandswhenever you can; without it double bets are refunded.cardevents already sent are still used. - The platform always recomputes ranks and winners from the cards and only compares the source's
handsandwinnersagainst them. It checks that the 21 cards are all different (one deck per round) and that the ranks and winners match the cards (11–13 at a table with special hands off is a mismatch). When cards come one by one it also checks that the first card comes first, that the n-th card after it goes to the position the first card dictates, that no position is dealt twice, and that the result's cards match the cards dealt. Any mismatch counts asRESULT_INCONSISTENTand is handled by trust level. - Deal order: a first card of A 5 9 K starts with the banker, 2 6 10 with player 1, 3 7 J with player 2, 4 8 Q with player 3; the deal then goes round banker → player 1 → player 2 → player 3, 5 cards each. In the example the first card is 7♥, so the cards go to
P2-1,P3-1,B-1,P1-1,P2-2…; the banker has Bull 8, player 1 Bull 9 wins, player 2 Bull 3 loses, player 3 Niu Niu wins. - Sessions: a fresh deck every round means there is no shoe, so
shoeis the session (for example sendshoe.startfor a new session every 60 rounds or at a dealer change) androundis the round within the session;decksintable.infoshould be1.
Events for dealing and revealing (card by card is recommended, with capabilities.cardByCard: true in table.info):
round.open→round.close(the first card also closes betting at once).- The first card:
card {"pos": "F", "card": "7H"}. It is always dealt face up;Fcannot carryfaceDown: true. - The 20 cards in the order the first card dictates, with
faceDown: truefor cards dealt face down:card {"pos": "P2-1", "card": "2D", "faceDown": true}. The platform stores the card but keeps it secret, telling players only that a card was dealt to player 2; cards withoutfaceDownare face up and shown as usual. - Reveal the hands:
reveal {"hands": ["P1"]}, preferably once per hand in the order player 1 → player 2 → player 3 → banker (one event may reveal several hands); the platform shows those hands' cards.revealis for Niu Niu only (other games getINVALID_EVENT) and, like other round events, needsshoe,roundandroundKey. round.result(complete: true, 21 cards). Hands not revealed yet are revealed by the platform in the order player 1 → player 2 → player 3 → banker before the result is shown.
Timing and betting window
- The source's
closesAtis the authoritative betting deadline. The platform closes betting at the earliest of the following, about 1 second early (to absorb player latency):closesAtadjusted by the estimated clock offset- when
round.openwas received plus the table's maximum betting time - when
round.closeor the firstcardwas received
- Sources without the
countdowncapability are timed withbetSecondsor the table setting. - Clock sync: heartbeats carry the source time, from which the platform estimates the clock offset and monitors latency. Closing betting on time depends on a correct clock, so always use NTP.