Skip to main content
A single trade crosses four channels and changes names twice on the way. This page maps a trade’s full lifecycle across every channel side by side, and lists the field aliases and noun changes you’d otherwise piece together from How parlays work, Real time & data channels, WebSocket, webhooks, and the Quote Service reference pages.

Naming conventions across channels

Each channel has its own event naming grammar. Knowing the pattern up front makes every table below scannable at a glance:
  • SSE events are bare or underscore separated names — best_quote, quote_request, quote_request_expired.
  • WebSocket events are resource:verb, colon namespaced — quote:accepted, position:settled.
  • Webhook events are resource.verb, dot namespaced — position.settled, parlay.status_changed.
There’s no deeper logic to the separator choice beyond which channel it’s on. The MM SSE stream isn’t even internally consistent: quote_request and quote_request_expired use underscores, while quote_request:updated uses a colon (see the summary table on that page).

One trade, across every channel

Read this left to right for any given moment, or top to bottom to follow one trade start to finish. ”—” means the moment has no representation on that channel.
quote:* WebSocket events (quote:accepted, quote:confirmed, quote:executed, quote:expired, quote:rejected, quote:withdrawn) deliver only to the awarded market maker, on their private mm:quotes:{mm_id} channel. position:* events deliver on rfq:{rfq_id}, the channel a user (or anyone tracking a specific trade) subscribes to. The two families are not interchangeable views of the same moment — several rows below have a WebSocket event for the MM and nothing for the user, or vice versa.
Lifecycle momentSSE — user stream (/v1/quote-requests/{id}/stream)SSE — MM stream (/v1/mm/quote-requests/stream)WebSocketWebhookREST status
Quote request created— (no dedicated “created” event; the stream opens after creation and the first frame is best_quote, often null)quote_requestnot delivered on this channel — WebSocket starts at quote acceptancenot delivered on this channel — webhooks are posttrade onlyquote request status: "active"GET /v1/quote-requests/{id}
Quote updated (price changes, or legs/amount edited)best_quotequote_request:updated — only fires when the request itself changes (PATCH); no MM stream event exists for an individual quote submission, yours or a competitor’snot delivered on this channelnot delivered on this channelquote request version/book_seq increments; status stays "active"
Quote accepted / committedstatus (status: "committed") followed immediately by the terminal committed event, then the stream closesquote_request_expired (reason: "committed"; won: true for the winning MM, false for every other MM)quote:accepted + mm_quote:acceptedMM only. Nothing pushes to the user on WebSocket; they already have the outcome from the synchronous commit responseparlay.status_changed (status: "accepted", accepted_quote_id set)RFQ status: "accepted"GET /v1/rfqs/{id}
Confirmed by makernot delivered on this channel — the stream already closed at commitnot delivered on this channelquote:confirmedMM only. No user facing WebSocket event marks this step specificallyparlay.status_changed (status: "confirmed")RFQ status: "confirmed"
Executed on chainnot delivered on this channelnot delivered on this channelquote:executed (MM channel) and position:created (rfq:{rfq_id} channel — collateral locked, position_pda set)parlay.status_changed (status: "executed")RFQ status: "executed"
Settlednot delivered on this channelnot delivered on this channelposition:settled (outcome, tx_signature)position.settled — the terminal outcome event; parlay.status_changed’s status values stop at executed/cancelled and never include "settled"RFQ status: "settled"
Bought back early (cashout)not on this stream — see the separate cashout-request stream, whose own terminal status event reports "committed"cashout_request announces only the opening of the buyback request to the backing MM; there’s no separate SSE event for its completionnot delivered on this channel — see the gap belowposition.bought_back (amount, mm_pays_user, tx_signature)RFQ status: "bought_back", cashout object populated
Cancelledcancelled — quote request abandoned precommit via Cancelquote_request_expired (reason: "cancelled")position:cancelled — only for a postcommit cancellation (e.g. the maker missed the confirmation deadline); a precommit quote request cancellation never reaches a position, so nothing fires herenot delivered precommit; postcommit fires both parlay.status_changed (status: "cancelled") and position.cancelled together (dedupe on X-Totalis-Event-Id)quote request status: "cancelled" precommit, or RFQ status: "cancelled" postcommit
Expiredexpired — quote request’s TTL lapsed with no commitquote_request_expired (reason: "expired")quote:expired — a specific MM’s accepted quote lapsed before confirmation; there’s no distinct WebSocket event for a whole RFQ expiringnot delivered on this channel — no webhook event exists for expiry, before or after commitquote request status: "expired" precommit, or RFQ status: "expired" postcommit

The WebSocket buyback gap

position.bought_back is a webhook only event. There is no position:bought_back (or equivalent) WebSocket event today. A bot or live UI holding only a WebSocket connection — the setup Real time & data channels and WebSocket recommend for latency sensitive trading — gets no real time signal that a position was bought out early.If your integration needs that event live, you must also configure a webhook endpoint to receive position.bought_back, or poll GET /v1/rfqs/{id} and watch for status: "bought_back". A WebSocket only integration will miss this moment until it reconciles.

Same value, different name

The same economic figures go by different names at the quote stage versus the settlement stage:
Quote stage nameSettlement stage nameSame value because
user_cost (Quote object — quote-request stream, GET /v1/quote-requests/{id})user_stake (Settlement object on GET /v1/rfqs/{id}; also the name used from the start in MM-stream and cashout payloads)Both are the user’s net stake (bet_amount minus the taker fee). The API renames it once the trade moves past pricing.
mm_cost (Quote object, quote stage)mm_risk (Settlement object; also used in MM stream cashout_request payloads)Both are the market maker’s amount at risk (total_payout/payout minus the user’s stake).
total_payout (Quote object, quote stage)payout (Settlement object, settlement stage)Both are the full amount paid to the winner on a win (user stake + mm risk).

Market maker identity across channels

mm_id, market_maker_id, and target_mm_id all identify the same thing — the market maker quoting or backing a trade. Which spelling you see depends on the channel:
FieldWhere it appears
mm_idThe MM SSE stream’s connected event; the mm:quotes:{mm_id} WebSocket channel name itself
market_maker_idQuote objects — best_quote.market_maker_id on both the quote-request stream and cashout stream, the Get quote request response, and the mm_quote:accepted WebSocket payload
target_mm_idThe cashout request object — the single MM a given cashout request is offered to

The noun stack

The same trade is called something different at each stage of its life. How parlays work introduces this progression; here it’s gathered in one place:
StageNameNotes
Precommit, ephemeralquote requestLives only in the Quote Service. Has its own id and status (activecommitted/cancelled/expired). Ceases to exist once it commits or dies — it never becomes a queryable historical record.
Postcommit, the API objectRFQCreated at commit, with its own rfq_id and status progression (openquotedacceptedconfirmedexecutedsettled/bought_back, or cancelled/expired). This is what GET /v1/rfqs/{id} returns.
Product facing nameparlayThe same object as the RFQ — “parlay” is the name you’d use in a UI or in conversation. The docs use “parlay (RFQ)” interchangeably for this reason.
Once funded on chainpositionThe RFQ’s on chain footprint once status >= confirmed. Carries position_id (the 16 byte on chain id, used specifically to open a cashout request) and position_pda (the on chain account address, surfaced on WebSocket position:* events). Look a position up by rfq_id/position_id via the API — the PDA is an on chain address, not a lookup key.