Skip to main content
The user feed streams your account‑scoped updates (orders, fills, cancels).

Messages

The user feed sends each update as a bare JSON object (no tuple envelope). All updates share one envelope; the action is determined by which of unmatched / matched are populated and by origin.

Common envelope

messageID is the stream entry id added by the streaming-api before send. Persist the latest one you have processed — it is your replay cursor. See Replaying missed messages below.

The matched block

Present whenever the update represents a fill (matched != null). Every field below is sent on every fill unless marked conditional. All values are from your perspective — as taker you see the side you took, as maker you see your posted side.
Do not derive win from risk. risk and win are each rounded to 2dp independently from the unrounded stake, so risk x odds can disagree with win by a cent. Book the win value as sent; if it is missing, treat the message as malformed rather than reconstructing it.Do not treat amount as a synonym for risk. On a taker fill amount excludes the commission charged on a loss and risk includes it. Booking amount as your risk will under-count commission on every take.
The same quantities over REST (get matched bets) are string-formatted to 2 decimals rather than numbers, and are computed with a slightly different commission and rounding order. Reconcile with a cent of tolerance rather than exact equality.

The unmatched block

Present whenever the update concerns an order resting on (or leaving) the book — placements, cancellations, and the maker side of a fill. Odds and sides are from the maker’s perspective, i.e. what you posted, not what the book offers others.
filled and remaining are not the same kind of number. remaining is running state — what is left on the book right now — but on a fill filled describes only that event. Across several partial fills they do not add up: filled + remaining equals offered on the first fill and drifts apart after that.To track how much of a resting order has been matched, use offered - remaining, or accumulate filled yourself. Do not mix the two.

How to identify the action

  • unmatched.filled === 0 && unmatched.offered > 0 -> order placed
  • unmatched.offered === 0 -> order cancelled
  • matched != null && unmatched == null -> matched as taker
  • matched != null && unmatched != null -> matched as maker (partial fill if unmatched.remaining > 0)
Updates for orders placed with orderType: "postArb" also carry isPostArb: true inside matched / unmatched (on placed, filled, and cancelled updates); the field is omitted otherwise.

Side and market semantics by bet type

The meaning of side (and which extra fields appear) inside matched / unmatched depends on type: market is only present on moneyline1x2 updates: it names the 3-way outcome the market is about (home team, away team, or the draw), and side says whether the bet is yes or no on that outcome. All fields are from your perspective — as maker you see your posted side; as taker you see the side you took.

Action: order placed

A new offer was put on the book. unmatched.filled is 0, unmatched.offered > 0, and matched is null.

Action: order cancelled

The user (or the system on their behalf) cancelled an offer. The distinguishing field is unmatched.offered === 0.

Action: order matched (taker)

The user took someone else’s offer. unmatched is null, matched carries the fill, and origin is "wager".

Action: order matched (maker, partial fill)

Someone hit the user’s posted offer. Both matched and unmatched are populated; unmatched.remaining shows what is still on the book. Because unmatched is set, origin is "offer".

Action: order matched (moneyline1x2)

Fills on 3-way soccer markets carry the market + side pair described above instead of a participant-hex side. Here the user took “yes” on the home team at +120 (a bet that Arsenal wins):
Note the two perspectives of the same fill: the taker sees side: "yes" at +120, the maker sees side: "no" at -120 on the same market.

Replaying missed messages

The v2 user feed supports gap recovery after a dropped connection. Every message carries a messageID (a monotonically increasing stream entry id, e.g. "1751552702114-0"). If your connection drops, you can fetch exactly the messages you missed.

Endpoint

Authentication is the same as the WebSocket: token in the Authorization header, Auth header, or token query parameter. Only messages belonging to the authenticated user are returned.

Response: success

Your afterID was still present in the retained history, so the returned list is a complete gap-fill — nothing between afterID and beforeID was lost:
Apply the messages in order and resume processing live messages.

Response: cache_expired

Your afterID has aged out of the retained history, so the server cannot prove the replay is complete:
availableMessages is whatever is still retained, oldest first. Messages older than the retention window are not recoverable through replay — reconnect promptly rather than relying on long look-back.

Example: reconnecting client with replay

A complete Node.js client (keepalive ping/pong omitted for brevity — see the quickstart at the top of this page). Two things keep it correct: every message — live, buffered, or replayed — passes through one forward-only cursor check, and a failed replay reconnects rather than going live over the gap.