Reliability, health & errors
Delivery semantics
- At-least-once delivery: the platform de-duplicates on
(sourceId, table, seq).- Same
seq, same content (compared as normalized JSON, key order does not matter): ignored and acknowledged as usual. - Same
seq, different content:nack SEQ_CONFLICT.
- Same
- Ordered within a stream: sources must send in increasing
seq; there is no ordering across tables. - Gap detection: when an event's
prevdiffers from the lastseqthe platform accepted, the answer isresync {table, from}and the source must resend everything afterfrom. - Acks only after persistence: an ack is sent once events are stored, and acks are cumulative:
{"B001": 1538}means every event of that stream up to 1538 is accepted. - Resume after reconnecting: when a WebSocket connects,
welcome.resumegives the last acknowledgedseqof each stream; continue after it. HTTPS sources can callGET /ingest/v1/resume?tables=…. - Source retention: sources must keep at least 24 hours of events available for resending.
- When you cannot resend: send a
table.snapshotwithgap: trueand continue with live events. The snapshot does not need a matchingprev, but itsseqmust still be greater than the last acceptedseq; later events continue from the snapshot'sseq. Rounds left open during the gap become abnormal rounds and are voided and refunded after a timeout. - One owner per stream: a stream can only arrive over one WebSocket connection at a time. A new connection that declares the same table in
hellotakes it over, and further events for that table on the old connection getnack STREAM_TAKEN_OVER. - Unbound source tables: events are acknowledged and archived but not applied; they apply once the platform binds the table.
Responses
HTTPS (POST /ingest/v1/events, /validate) answers for the whole batch:
{
"acks": { "B001": 1538 },
"nacks": [{ "code": "INVALID_EVENT", "table": "B002", "seq": 77, "message": "data.result.cards.P1: must match ^(A|[2-9]|10|J|Q|K)[SHDC]$" }],
"resync": [{ "table": "B003", "from": 812 }]
}WebSocket sends {"op":"ack","acks":{…}}, {"op":"nack",…} and {"op":"resync","table":"…","from":…} messages instead.
- A
nackwithaccepted: true(RESULT_INCONSISTENT) means the event was accepted and stored and the stream moved on; it is only a warning. - Any other
nackmeans the event was not accepted and the stream stays at the previous event; fix it and resend with the sameseq. - After a
nackorresyncfor a stream within a batch, the rest of that stream's events in the batch are skipped; other streams are unaffected. - Every event, rejected ones included, goes into the raw archive for disputes and audits.
Health and heartbeats
Heartbeats (they use no stream sequence numbers) every 5 seconds report the health of each table as
ok,degradedordown:json{"op":"heartbeat","ts":"2026-09-24T03:00:35.000Z","tables":{"B001":"ok","B002":"down"}}When a source knows a table's upstream is down, it must mark the table
downimmediately; the platform stops betting on it at once.No heartbeat and no event for 15 seconds means the source is lost: all its bound tables stop taking bets; open rounds wait for recovery and are voided and refunded after a timeout.
When the last WebSocket connection drops, the tables it carried stop taking bets immediately; they recover when the source reconnects and sends a heartbeat or events.
Error codes
| Code | Meaning | What the source should do |
|---|---|---|
UNAUTHORIZED | Bad signature, outside the time window, reused nonce, transport not enabled or IP not allowed | Check the key, clock and settings |
SOURCE_DISABLED | The source is disabled | Contact the platform |
UNSUPPORTED_VERSION | Unsupported v | Upgrade |
TABLE_NOT_ALLOWED | The source may not publish this table | Contact the platform |
INVALID_EVENT | Structure or field errors | Fix the event and resend with the same seq |
SEQ_GAP (resync) | prev does not match | Resend after from |
SEQ_CONFLICT | Same seq with different content | A bug at the source; investigate |
RESULT_CONFLICT | Different result with the same rev (reserved; this version keeps the original result, records it and sends no nack) | Use round.correct |
RESULT_INCONSISTENT | Cards do not match the points, winner or drawing rules (Niu Niu: a repeated card, ranks or winners that do not match the cards, or cards dealt out of order; see Niu Niu) | Accepted but held for review; send round.correct to fix it |
STREAM_TAKEN_OVER | A newer connection took over this table | Stop sending the table on the old connection |
RATE_LIMITED | Too much traffic (reserved; not returned in this version) | Back off and retry |
WebSocket close codes:
| Code | Meaning |
|---|---|
4001 | Authentication failed (for example hello.source does not match the credentials) |
4002 | Unsupported version |
4003 | The source is disabled |
4008 | Limit violated (message or batch too large) |
1012 | The platform is restarting: reconnect at once and resume |
Reconnect backoff: start at 1 second, double each time, cap at 30 seconds, plus random jitter.
Source trust levels
The platform assigns each source a trust level that decides how much is automated. Every case is handled automatically; only the guardrails differ:
| Level | Incomplete result (complete: false) | Result check fails | Correction (round.correct) | Void after settlement (round.cancel) |
|---|---|---|---|---|
trusted | Zones that can be settled are settled, the rest refunded | Settled on the source's winner and flagged | Recalculated automatically | Reversed automatically |
standard | Same | Held unsettled; voided and refunded after a timeout | Recalculated within the guardrails; outside them the original stands and the round is disputed | Same as left |
restricted | Same | Same | Same | Same |
Guardrails: the correction or void arrives within 30 minutes of settlement, and the new result is complete and passes the drawing rules. Outside the guardrails no money moves; the round is only recorded as disputed.
WebSocket session example
← {"op":"welcome","v":1,"session":"s_8f2…","heartbeat":5,"maxBatch":500,"maxInflight":2000,
"resume":{"B001":1530,"D001":0},"serverTime":"2026-09-24T03:00:29.950Z"}
→ {"op":"hello","v":1,"source":"acme-live","agent":"acme-feeder/2.0",
"tables":["B001","D001"],"capabilities":{"cardByCard":true,"countdown":true,"snapshot":true,"replayHours":168}}
→ {"op":"events","events":[
{"v":1,"type":"round.open","table":"B001","seq":1531,"prev":1530,"ts":"2026-09-24T03:00:30.000Z",
"game":"baccarat","shoe":"260924-03","round":12,"roundKey":"B001-260924-03-12",
"data":{"closesAt":"2026-09-24T03:00:55.000Z","betSeconds":25}}]}
← {"op":"ack","acks":{"B001":1531}}
→ {"op":"heartbeat","ts":"2026-09-24T03:00:35.000Z","tables":{"B001":"ok","D001":"ok"}}
→ {"op":"events","events":[
{"v":1,"type":"round.close","table":"B001","seq":1532,"prev":1531,"ts":"2026-09-24T03:00:55.010Z",
"game":"baccarat","shoe":"260924-03","round":12,"roundKey":"B001-260924-03-12","data":{}},
{"v":1,"type":"round.result","table":"B001","seq":1538,"prev":1532,"ts":"2026-09-24T03:01:20.400Z",
"game":"baccarat","shoe":"260924-03","round":12,"roundKey":"B001-260924-03-12",
"data":{"rev":1,"complete":true,"result":{
"cards":{"P1":"2C","B1":"4H","P2":"3D","B2":"2S","P3":"10S"},
"player":5,"banker":6,"winner":"B","playerPair":false,"bankerPair":false}}}]}
← {"op":"ack","acks":{"B001":1538}}seqmay skip numbers (1532 is followed by 1538 above), but eachprevmust equal the previousseq.hello.sourcemust equalX-Source-Id, otherwise the connection is closed with4001.hello.tablesdeclares the tables this connection carries; a new connection takes over the same tables.
Going live
- Ask the platform for a source: ID, type, transports and IP allowlist → you receive an HMAC key.
- Integrate against the test environment (
elite-dev-ingest.ewin888.com) first; usePOST /ingest/v1/validatefor dry runs, or check formats with the GFI event validator. - Pass the compatibility checks: resume after disconnects (from any
seq), resending onresync, sending a snapshot when you cannot resend, heartbeats anddownmarking, corrections and voids, clock offset < 500 ms, and handling of rejected events. - The platform binds your source tables to public tables, sets priorities and goes live.