> For the complete documentation index, see [llms.txt](https://mute-sh.gitbook.io/mute.sh/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mute-sh.gitbook.io/mute.sh/reference/ws-events.md).

# WebSocket Events

Cross-channel reference for the Parlay v2 relay's two WebSocket channels — the maker (RFQ) channel and the taker channel. Every message type below is verified against the live relay's real wire types; there is no v1 compatibility mode. For the maker channel's full field-level protocol spec (auth handshake details, winner-selection rules, the complete error model), see [Maker Protocol Spec](/mute.sh/reference/maker-protocol-spec.md) — this page focuses on giving both channels' event catalogs side by side and the cross-channel sequence diagrams.

{% hint style="info" %}
**Maker channel:** `wss://<host>/v2/ws/rfq` (production: `wss://parlay.mute.sh/v2/ws/rfq`) **Taker channel:** `wss://<host>/v2/ws/taker` (production: `wss://parlay.mute.sh/v2/ws/taker`) All messages are UTF-8 JSON frames — one JSON object per frame, no batching. The public URL's `/v2` segment is stripped by the edge proxy; the relay itself listens on the bare `/ws/rfq` and `/ws/taker` paths.
{% endhint %}

{% hint style="danger" %}
This page describes the real, currently-deployed v2 wire protocol. If you have integrated against an older reference that used `hello`/`welcome`, `rfq`/`quote`/`quote:reject`, or `lastlook`/`lastlook:ack` on the **maker** channel, or an 11-field `ParlayQuote` with a signed `spreadBps` — that was a different, earlier protocol generation and none of those message types exist on this relay. The maker channel's real message types are `auth`, `RFQ_QUOTE`, `RFQ_QUOTE_CANCEL`, `RFQ_CONFIRMATION_RESPONSE`, `CASHOUT_OFFER`, `CASHOUT_CANCEL`, `ping` (client→server) and `auth`, `RFQ_REQUEST`, `ACK_RFQ_QUOTE`, `ACK_RFQ_QUOTE_CANCEL`, `RFQ_CONFIRMATION_REQUEST`, `ACK_RFQ_CONFIRMATION_RESPONSE`, `RFQ_EXECUTION_UPDATE`, `RFQ_TRADE`, `RFQ_ERROR`, `pong` (server→client). See §2 for the full inventory.
{% endhint %}

***

## Table of Contents

1. [Connection](#1-connection)
2. [Quick Reference Tables](#2-quick-reference-tables)
3. [Maker Channel Events](#3-maker-channel-events)
   * 3.1 [Server → Maker: `auth` (response)](#31-server--maker-auth-response)
   * 3.2 [Server → Maker: `RFQ_REQUEST`](#32-server--maker-rfq_request)
   * 3.3 [Server → Maker: `RFQ_CONFIRMATION_REQUEST`](#33-server--maker-rfq_confirmation_request)
   * 3.4 [Server → Maker: `RFQ_EXECUTION_UPDATE`](#34-server--maker-rfq_execution_update)
   * 3.5 [Server → Maker: `RFQ_TRADE`](#35-server--maker-rfq_trade)
   * 3.6 [Server → Maker: `RFQ_ERROR`](#36-server--maker-rfq_error)
   * 3.7 [Maker → Server: `auth`](#37-maker--server-auth)
   * 3.8 [Maker → Server: `RFQ_QUOTE`](#38-maker--server-rfq_quote)
   * 3.9 [Maker → Server: `RFQ_CONFIRMATION_RESPONSE`](#39-maker--server-rfq_confirmation_response)
4. [Taker Channel Events](#4-taker-channel-events)
   * 4.1 [Server → Taker: `ready`](#41-server--taker-ready)
   * 4.2 [Taker → Server: `hello`](#42-taker--server-hello)
   * 4.3 [Taker → Server: `rfq`](#43-taker--server-rfq)
   * 4.4 [Server → Taker: `quotes`](#44-server--taker-quotes)
   * 4.5 [Taker → Server: `prepare` / `accept`](#45-taker--server-prepare--accept)
   * 4.6 [Server → Taker: `RFQ_EXECUTION_UPDATE` / `RFQ_SNAPSHOT`](#46-server--taker-rfq_execution_update--rfq_snapshot)
   * 4.7 [Cashout push events: `cashout:offer` / `cashout:executed`](#47-cashout-push-events-cashoutoffer--cashoutexecuted)
5. [Sequence Diagrams](#5-sequence-diagrams)
   * 5.1 [Happy-Path Flow (no last look)](#51-happy-path-flow-no-last-look)
   * 5.2 [Last-Look Flow](#52-last-look-flow)
   * 5.3 [Fill vs. Settlement](#53-fill-vs-settlement)
6. [Reconnection Behavior](#6-reconnection-behavior)
7. [Security Notes](#7-security-notes)

***

## 1. Connection

### Channels

```
wss://<host>/v2/ws/rfq     — Maker channel (authenticated, drives quoting)
wss://<host>/v2/ws/taker   — Taker channel (unauthenticated, drives the RFQ lifecycle)
```

All messages are UTF-8 JSON frames. One JSON object per frame; no batching.

### Taker channel

Takers connect to `/v2/ws/taker` with **no authentication**. On connect, the server immediately sends a `ready` frame containing the escrow address, chain ID, order-size limits, current combo markets, the full deployed contract stack, and the live connected-maker count. The taker then drives the entire RFQ lifecycle over this one socket: `hello` (subscribe for pushes), `rfq` (create a request), live `quotes` pushes, `prepare`, `accept`, and `status`. There is no REST equivalent for the live quote stream — it only exists as a WS push on this channel.

The taker channel requires no proof of address control for the ordinary RFQ flow, because a taker only ever receives pushes for RFQs it created itself in the same session. Cashout pushes (see §4.7) are the one exception — they concern a pre-existing on-chain position the subscriber didn't necessarily create in this session, so they require an additional signed proof of address control.

### Maker channel

Makers authenticate by sending an `auth` frame after the socket opens. The server has a 30-second auth timeout — if no successful `auth` arrives in that window, the socket is closed with code `1008`. The server validates the API key and identity fields and, if accepted, responds with `{type:"auth", success:true, address, role:"maker"}`. Any message type other than `auth` sent before a successful auth returns an `RFQ_ERROR{code:"UNAUTHENTICATED"}` — it is not silently dropped, and the connection is not immediately closed (the auth timer will still close it if auth never succeeds).

There is **no backfill of already-open RFQs on connect or reconnect**. A maker that connects (or reconnects after a drop) only sees `RFQ_REQUEST` broadcasts that go out *after* its `auth` succeeds. If quoting continuity across reconnects matters to your strategy, keep your reconnect time short and accept that any RFQ that both opened and closed its (short, sub-second) quote window while you were disconnected is simply missed — there is nothing to page in afterward.

***

## 2. Quick Reference Tables

### A. Maker channel — Server → Maker

| Message                         | When sent                                                                                        | Maker should respond?                                      |
| ------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- |
| `auth` (response)               | Immediately after the maker's `auth` request                                                     | No                                                         |
| `RFQ_REQUEST`                   | New taker RFQ created; broadcast to every authenticated maker                                    | Optional — send `RFQ_QUOTE` to compete, or ignore          |
| `ACK_RFQ_QUOTE`                 | Acknowledges a `RFQ_QUOTE` was accepted into the book                                            | No                                                         |
| `ACK_RFQ_QUOTE_CANCEL`          | Acknowledges a `RFQ_QUOTE_CANCEL`                                                                | No                                                         |
| `RFQ_CONFIRMATION_REQUEST`      | This maker's quote won and it opted into last-look at auth                                       | Yes — send `RFQ_CONFIRMATION_RESPONSE` before `confirm_by` |
| `ACK_RFQ_CONFIRMATION_RESPONSE` | Acknowledges a `RFQ_CONFIRMATION_RESPONSE`                                                       | No                                                         |
| `RFQ_EXECUTION_UPDATE`          | Sent to the winning maker only, at `MATCHED` (tx submitted) and again at `CONFIRMED` or `FAILED` | No                                                         |
| `RFQ_TRADE`                     | Broadcast to **every** connected maker (winners and losers) once a trade executes                | No                                                         |
| `RFQ_ERROR`                     | Any validation failure tied to a maker's request                                                 | No (maker may retry or fix and resend)                     |
| `pong`                          | Reply to the maker's `ping`                                                                      | No                                                         |

### B. Maker channel — Maker → Server

| Message                     | When sent                                            | Server action                                                                                              |
| --------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `auth`                      | Immediately on connect, before any other message     | Validates API key + identity; responds `auth`                                                              |
| `RFQ_QUOTE`                 | In response to an `RFQ_REQUEST`                      | Validates the signed quote; on success sends `ACK_RFQ_QUOTE`, may trigger `RFQ_CONFIRMATION_REQUEST` later |
| `RFQ_QUOTE_CANCEL`          | To withdraw a live quote                             | Removes it from the book if not already locked in as the winner                                            |
| `RFQ_CONFIRMATION_RESPONSE` | In response to a `RFQ_CONFIRMATION_REQUEST`          | `CONFIRM` proceeds to execution; `DECLINE` triggers fallback to the next-best quote                        |
| `CASHOUT_OFFER`             | To post a signed early-payout offer on a live parlay | Validates + records; pushes `cashout:offer` to the position's taker                                        |
| `CASHOUT_CANCEL`            | To withdraw a cashout offer                          | Removes it if not locked                                                                                   |
| `ping`                      | Liveness check                                       | Replies `pong`                                                                                             |

### C. Taker channel — Taker → Server

| Message   | When sent                                         | Server action                                                                      |
| --------- | ------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `hello`   | To subscribe this socket for pushes on an address | Registers the socket; optionally verifies a signature for cashout-push eligibility |
| `rfq`     | To create a new RFQ                               | Creates the RFQ, replies `rfq:created`, begins pushing `quotes`                    |
| `prepare` | Once a quote is chosen                            | Returns the exact order + permit structs to sign                                   |
| `accept`  | After signing the prepared order and permit       | Locks in the quote; triggers last-look or immediate execution                      |
| `status`  | To poll an RFQ's current state                    | Returns the current `RFQSnapshot`                                                  |
| `ping`    | Liveness check                                    | Replies `pong`                                                                     |

### D. Taker channel — Server → Taker

| Message                | When emitted                                                                                    |
| ---------------------- | ----------------------------------------------------------------------------------------------- |
| `ready`                | Immediately on connect                                                                          |
| `hello:ok`             | Reply to `hello`                                                                                |
| `rfq:created`          | Reply to `rfq`, with the initial snapshot                                                       |
| `quotes`               | Pushed every time the live quote book for an open RFQ changes                                   |
| `prepared`             | Reply to `prepare`                                                                              |
| `accepted`             | Reply to `accept` (acknowledgement only — see §5.3 on why this is not the fill)                 |
| `rfq:state`            | Reply to `status`                                                                               |
| `RFQ_SNAPSHOT`         | Pushed whenever the RFQ's state changes (quote selected, last-look outcome, termination)        |
| `RFQ_EXECUTION_UPDATE` | Pushed at `MATCHED` (tx submitted) and again at `CONFIRMED` or `FAILED`                         |
| `cashout:offer`        | Pushed to a cashout-subscribed taker when the best live cashout offer on their position changes |
| `cashout:executed`     | Pushed once a cashout novation is confirmed on-chain                                            |
| `rfq:error`            | Any error tied to a taker's request                                                             |
| `pong`                 | Reply to the taker's `ping`                                                                     |

***

## 3. Maker Channel Events

The maker channel's messages are typed exactly as shown below — this is the same set documented in depth (including full field tables and validation rules) in [Maker Protocol Spec](/mute.sh/reference/maker-protocol-spec.md). This section gives the schema-level summary; consult that page for field-by-field semantics, the winner-selection algorithm, and the complete `RfqErrorCode` list.

### 3.1 Server → Maker: `auth` (response)

```json
{ "type": "auth", "success": true, "address": "0xMaker...", "role": "maker" }
```

On failure: `{ "type": "auth", "success": false, "error": "invalid apiKey" }` (or `"apiKey required"`, `"maker_address and signer_address required"`).

### 3.2 Server → Maker: `RFQ_REQUEST`

Broadcast to every authenticated maker the instant a taker creates an RFQ. Contains the RFQ's `rfq_id`, canonical `leg_position_ids`, `condition_id` (the on-chain `legsHash` the maker's quote must commit to), `yes_position_id`/`no_position_id`, `direction`, `side`, `requested_size`, `submission_deadline`, and an `escrow_terms` block (chain ID, escrow address, taker address, builder, gross/net stake, `protocol_fee_bps`, `builder_fee_bps`, `spread_bps`). Full field table: [Maker Protocol Spec §4](/mute.sh/reference/maker-protocol-spec.md).

There is no advisory "probability" field and no per-leg human label on this message — legs are addressed purely by `leg_position_id` (HIP-4 outcome/side encoding). Market metadata (titles, images) is looked up separately via the combo-markets listing, not embedded in the RFQ push.

### 3.3 Server → Maker: `RFQ_CONFIRMATION_REQUEST`

Sent only to the maker whose quote just won, and only if that maker opted into last-look (`last_look: true`) at auth time. Carries `rfq_id`, `quote_id`, the RFQ's identity/leg fields, `fill_size_e6`, `price_e6`, and `confirm_by` (a unix-ms deadline). The maker must reply with `RFQ_CONFIRMATION_RESPONSE` before `confirm_by`, or the server treats it as a decline (see §5.2 — this is fail-closed, not fail-open).

### 3.4 Server → Maker: `RFQ_EXECUTION_UPDATE`

Sent to the winning maker (and, on the taker channel, to the taker) as execution progresses:

```json
{ "type": "RFQ_EXECUTION_UPDATE", "rfq_id": "...", "status": "MATCHED", "tx_hash": "0x..." }
```

then later either:

```json
{ "type": "RFQ_EXECUTION_UPDATE", "rfq_id": "...", "status": "CONFIRMED", "tx_hash": "0x...", "parlay_id": "1234" }
```

or:

```json
{ "type": "RFQ_EXECUTION_UPDATE", "rfq_id": "...", "status": "FAILED" }
```

`MATCHED` fires as soon as the transaction is submitted (not yet mined) — it is your earliest confirmation the trade is in flight. `CONFIRMED` fires only after the transaction is mined and the `ParlayCreated` event is found in the receipt; it is the first message that carries the on-chain `parlay_id`. `FAILED` fires on any submission or revert error and is always paired with a separate `RFQ_ERROR{code:"TRADE_SUBMISSION_FAILED"}` carrying the underlying error message. The type also reserves `"MINED"` and `"RETRYING"` status values for future use — the current relay does not emit them; only `MATCHED`, `CONFIRMED`, and `FAILED` occur in practice today.

This is the fill/execution signal. It is not settlement — see [§5.3](#53-fill-vs-settlement).

### 3.5 Server → Maker: `RFQ_TRADE`

Broadcast to **every currently-connected maker** — winners and losers alike — the moment a trade executes, so the whole maker pool sees realized market activity even if they didn't win the auction:

```json
{
  "type": "RFQ_TRADE", "rfq_id": "...", "requester_id": "0xTaker...",
  "condition_id": "0x...", "leg_position_ids": ["#1100", "#1200"],
  "direction": "BUY", "side": "YES", "price_e6": "410000", "size_e6": "500000000",
  "executed_at": 1751410860000
}
```

### 3.6 Server → Maker: `RFQ_ERROR`

```json
{ "type": "RFQ_ERROR", "code": "QUOTE_MISMATCH", "error": "legsHash mismatch: expected 0x...", "rfq_id": "...", "request_type": "RFQ_QUOTE" }
```

`code` is a stable, typed value from a fixed 28-value error-code enum shared across REST and WS — see [Maker Protocol Spec §9, Error model](/mute.sh/reference/maker-protocol-spec.md#9-error-model) for the complete list. The `error` string is free text for humans and is not part of the stable contract; only `code` should drive programmatic handling.

### 3.7 Maker → Server: `auth`

```json
{
  "type": "auth",
  "auth": { "apiKey": "..." },
  "identity": { "signer_address": "0x...", "maker_address": "0x...", "signature_type": 0 },
  "last_look": false
}
```

`signature_type` must be `0` (EOA) — any other value is rejected with `INVALID_IDENTITY`. `last_look` is this maker's opt-in for the confirmation gate; default `false` (immediate execution on win). A standing EIP-2612 `approval` for the escrow can optionally be included in the same `auth` frame — see [Maker Protocol Spec §8, Standing allowance](/mute.sh/reference/maker-protocol-spec.md#8-standing-allowance-eip-2612) for the standing-allowance mechanics.

### 3.8 Maker → Server: `RFQ_QUOTE`

```json
{ "type": "RFQ_QUOTE", "rfq_id": "...", "price_e6": "410000", "size_e6": "500000000", "signed_order": { "...": "the 10-field EIP-712 ParlayQuote" } }
```

`signed_order` is the maker-signed `ParlayQuote` struct (`maker, taker, legsHash, takerCollateral, makerCollateral, builder, protocolFeeBps, builderFeeBps, nonce, deadline` — ten fields, no `spreadBps`; spread is owner-configured escrow state, not maker-signed). The server cross-checks `legsHash`, `taker`, `builder`, `protocolFeeBps`, `builderFeeBps`, `takerCollateral`, and the `price_e6`/`size_e6` arithmetic against the RFQ's authoritative values before accepting the quote — any mismatch returns `RFQ_ERROR{code:"QUOTE_MISMATCH"}`.

### 3.9 Maker → Server: `RFQ_CONFIRMATION_RESPONSE`

```json
{ "type": "RFQ_CONFIRMATION_RESPONSE", "rfq_id": "...", "quote_id": "...", "decision": "CONFIRM" }
```

Only the currently-winning maker for that RFQ may send this — any other maker gets `RFQ_ERROR{code:"UNAUTHORIZED_ROLE"}`.

***

## 4. Taker Channel Events

### 4.1 Server → Taker: `ready`

Sent once, immediately on connect:

```json
{
  "type": "ready",
  "escrow": "0x551D4A7EdB71583C7C12900128727D857e308925",
  "chainId": 998,
  "limits": { "minLegs": 2, "maxLegs": 6, "minStakeUsd": 1, "maxStakeUsd": 10000 },
  "markets": [ "...combo markets..." ],
  "stack": { "chainId": 998, "escrow": "0x...", "usdc": "0x...", "registry": "0x...", "signalConsensus": "0x...", "resolutionRequester": "0x..." },
  "makers": { "connected": 5 }
}
```

`stack` is the full deployed contract set this relay instance targets — cross-check it against your own configuration before trusting a later `prepare` response, since it's the one place the complete address set is exposed in one message. `limits` values are live config, not fixed constants — read them here rather than hardcoding a leg-count or stake bound.

### 4.2 Taker → Server: `hello`

```json
{ "type": "hello", "address": "0xTaker..." }
```

Subscribes this socket to pushes for `address` — RFQ creation, quote updates, execution status, and snapshots for any RFQ this taker creates on this connection. The server replies `{ "type": "hello:ok", "address": "0x...", "verified": false }`.

An **optional** signed variant additionally unlocks cashout pushes for that address (see §4.7): include `signature` (and `ts`) signing a fixed, domain-separated message binding the address and timestamp. If the signature verifies and the timestamp is fresh (within a several-minute skew window), the response's `verified` field is `true` and this socket will also receive `cashout:offer`/`cashout:executed` pushes for that address. Plain unsigned `hello` is unchanged and still works for the ordinary RFQ flow — the signature is only required for the cashout-push privacy gate, not for creating or tracking your own RFQs.

### 4.3 Taker → Server: `rfq`

```json
{ "type": "rfq", "reqId": "r1", "taker": "0xTaker...", "legs": [{ "outcomeId": 110, "sideIndex": 0 }, { "outcomeId": 120, "sideIndex": 0 }], "stake_usd": 50 }
```

The server creates the RFQ, implicitly subscribes this socket to the taker's address, and replies `{ "type": "rfq:created", "reqId": "r1", "rfqId": "...", "snapshot": { "...": "RFQSnapshot" } }`. It then immediately begins broadcasting `RFQ_REQUEST` to makers and pushing `quotes` updates back on this socket as they arrive. The quote-collection window is short — a few hundred milliseconds by default — not the "about an hour" figure sometimes assumed; check `snapshot.request.submission_deadline` for the live value rather than hardcoding a duration.

For the full combo-identity rules (canonical leg ordering, `condition_id` derivation, and the conflict checks that can reject a malformed combo before it's ever broadcast to makers), see [Maker Protocol Spec §6, Combo identity](/mute.sh/reference/maker-protocol-spec.md#6-combo-identity-condition_id-position-ids-and-conflict-checks) — this page does not duplicate that taxonomy.

### 4.4 Server → Taker: `quotes`

Pushed every time the live quote book for an open RFQ changes:

```json
{
  "type": "quotes", "rfqId": "...", "count": 2, "bestQuoteId": "q_2",
  "quotes": [
    { "quoteId": "q_1", "maker": "0x...", "price_e6": "420000", "size_e6": "500000000", "makerCollateral": 290, "payout": 500, "netPayout": 497.5, "odds": 9.95, "best": false },
    { "quoteId": "q_2", "maker": "0x...", "price_e6": "410000", "size_e6": "500000000", "makerCollateral": 300, "payout": 500, "netPayout": 498.5, "odds": 9.97, "best": true }
  ]
}
```

`price_e6` is the taker's net-stake-implied probability (6dp); `best` marks the quote the server would currently select. There is no `mmId` field (makers are identified only by address) and no per-quote `expiresAt` surfaced here — treat every quote as perishable rather than relying on a TTL that isn't part of the wire contract. **Winner selection is lowest `price_e6`** (cheapest cost of a given payout for the taker), not highest collateral — ties break by earliest arrival.

### 4.5 Taker → Server: `prepare` / `accept`

```json
{ "type": "prepare", "reqId": "r2", "rfqId": "...", "quoteId": "q_2" }
```

replies `{ "type": "prepared", "reqId": "r2", "...": "the exact order + permit structs and domains to sign" }`. After signing both the `ParlayOrder` and the EIP-2612 permit client-side:

```json
{ "type": "accept", "reqId": "r3", "rfqId": "...", "quoteId": "q_2", "orderSig": "0x...", "permitSig": "0x..." }
```

replies `{ "type": "accepted", "reqId": "r3", "rfqId": "...", "snapshot": { "...": "RFQSnapshot" } }`. This acknowledgement is **not** the fill result — see below.

### 4.6 Server → Taker: `RFQ_EXECUTION_UPDATE` / `RFQ_SNAPSHOT`

`accept` returns immediately, before the on-chain transaction is even submitted. The actual execution outcome arrives asynchronously as a sequence of pushes on the same socket:

1. `RFQ_SNAPSHOT` — pushed whenever the RFQ's engine state changes (e.g. moving into `EXECUTING`, or into `AWAITING_MAKER_CONFIRMATION` if the winning maker uses last-look).
2. `RFQ_EXECUTION_UPDATE{status:"MATCHED", tx_hash}` — the transaction has been submitted (not yet mined).
3. Either `RFQ_EXECUTION_UPDATE{status:"CONFIRMED", tx_hash, parlay_id}` once the transaction is mined and the on-chain `parlay_id` is known — this is the actual fill — or `RFQ_EXECUTION_UPDATE{status:"FAILED"}` paired with `RFQ_ERROR{code:"TRADE_SUBMISSION_FAILED"}` on revert.

Poll `status` / watch for `RFQ_SNAPSHOT` to read `request` fields, and use `RFQ_EXECUTION_UPDATE` for the execution outcome specifically. Neither of these events is settlement — see [§5.3](#53-fill-vs-settlement).

### 4.7 Cashout push events: `cashout:offer` / `cashout:executed`

Only delivered to sockets that completed the **signed** `hello` (§4.2) for the position's taker address — an unsigned `hello` still works for the ordinary RFQ flow above but will not receive these, since they concern a pre-existing position the socket may not have created in this session.

```json
{ "type": "cashout:offer", "parlay_id": "1234", "offer": { "offer_id": "...", "maker": "0x...", "payout_e6": "...", "resolvedWonCount": 1, "voidedCount": 0, "nonce": "...", "deadline": 1751500000, "valid_until": 1751500000000 } }
```

`offer` is `null` if there is currently no live cashout offer on the position. Pushed both when a maker posts/cancels an offer and on a periodic background refresh of the position's on-chain resolution state (which can auto-invalidate a stale offer if a leg resolved in the meantime).

```json
{ "type": "cashout:executed", "parlay_id": "1234", "offer_id": "...", "payout_e6": "...", "new_owner": "0x...", "tx_hash": "0x..." }
```

Sent once a cashout novation is confirmed on-chain, to both the original taker and the maker who now owns the position. See [Cashout](/mute.sh/integration-guides/cashout.md) for the full novation flow. Note that `payout_e6` here is credited to the taker's withdrawable balance on the escrow, not sent to their wallet automatically — see [§5.3](#53-fill-vs-settlement) for the pull-payment model.

***

## 5. Sequence Diagrams

### 5.1 Happy-Path Flow (no last look)

![Happy-path RFQ sequence (no last look)](/files/tIF3vWcUJ63klyWIsSmE)

***

### 5.2 Last-Look Flow

Maker B authenticated with `last_look: true`. After winning, it gets a bounded confirmation window before the relay submits on-chain.

**Confirm path:**

![Last look — confirm path](/files/s5H7INHKRllPPlA0InO1)

**Decline / timeout path (fail-closed, with fallback):**

![Last look — decline / timeout path with fallback](/files/FDPUtkymXRO4QpGUTLMG)

If the maker explicitly sends `RFQ_CONFIRMATION_RESPONSE{decision:"DECLINE"}` or simply lets `confirm_by` pass, the outcome is identical: the relay marks that maker as declined for this RFQ, promotes the next-best live quote (up to a small, fixed number of fallback attempts), and requires the taker to re-prepare and re-accept against the new quote, since their signed order was bound to the specific declined quote's hash. If the fallback budget is exhausted or no other quote is available, the RFQ terminates as `REJECTED` and the taker must submit a new RFQ.

***

### 5.3 Fill vs. Settlement

Execution (fill) and settlement (payout) are **not the same event**, and only the former is actively pushed:

![Execution vs settlement](/files/k07j18hNRwNUujNy10tN)

`RFQ_EXECUTION_UPDATE` and `RFQ_TRADE` tell you the trade executed — that happens promptly, typically within one block after `accept`. They say nothing about whether the parlay eventually wins. Final settlement (each leg's outcome resolving, any dispute window elapsing, and the contract crediting the winner) happens later — potentially much later — and there is **no general WebSocket push for "your parlay has settled."** The only settlement-adjacent push in this catalog is the cashout position poll (§4.7), which is scoped to a taker who has an active, cashout-eligible position and has completed the signed `hello`; it is not a substitute for a general settlement notification.

Settlement is also a **pull-payment model**: a win credits the winner's withdrawable balance on the escrow contract, it does not transfer USDC to their wallet automatically. The recipient must separately call the escrow's withdraw function to actually receive funds. Track your own parlay status by watching the escrow's on-chain events directly, or by polling the cashout-position endpoint for parlays you're tracking cashout offers on.

***

## 6. Reconnection Behavior

**Maker channel**: there is no RFQ backfill of any kind. A maker that reconnects after a drop starts from a clean slate and only receives `RFQ_REQUEST` broadcasts for RFQs created after its new `auth` succeeds. Because the quote-collection window is short (sub-second by default), any RFQ that both opened and closed while a maker was disconnected is simply gone — there is no queue to drain and nothing to request a replay of.

**Taker channel**: reconnecting requires re-sending `hello` to re-subscribe the new socket for pushes, and (if cashout-push eligibility is needed) re-sending the signed variant to re-verify. In-flight RFQ state is not lost server-side — a `status` request against a known `rfqId` will return the current `RFQSnapshot` regardless of how many times the taker's socket has reconnected, since RFQ state lives in the relay's engine, not in the socket.

***

## 7. Security Notes

### Makers must verify `condition_id`/`legsHash` independently

The `condition_id` in `RFQ_REQUEST` is the value your signed `ParlayQuote.legsHash` must match exactly — the server will reject any quote whose `legsHash` doesn't match its own computation from the RFQ's legs. See [Maker Protocol Spec §6, Combo identity](/mute.sh/reference/maker-protocol-spec.md#6-combo-identity-condition_id-position-ids-and-conflict-checks) for the canonical leg-ordering and hashing rules; this page does not duplicate them.

### Fee and identity fields are server-enforced, not negotiated

The server enforces that a maker's `RFQ_QUOTE.signed_order` fields (`legsHash`, `taker`, `builder`, `protocolFeeBps`, `builderFeeBps`, `takerCollateral`) exactly match the RFQ's authoritative values, and that `price_e6`/`size_e6` are arithmetically consistent with the signed collaterals. Any mismatch is rejected with `RFQ_ERROR{code:"QUOTE_MISMATCH"}` before the quote enters the book. `spreadBps` is not part of the maker-signed struct at all in v2 — it is escrow-owner configuration applied at settlement, independent of what any maker quotes.

### Last-look is fail-closed

A maker that doesn't respond to `RFQ_CONFIRMATION_REQUEST` before `confirm_by` is treated exactly the same as an explicit `DECLINE` — never as an implicit accept. This protects the taker from an unresponsive maker stalling their trade indefinitely, at the cost of the maker losing the fill if it doesn't respond in time.

### Cashout pushes require proof of address control

Unlike the ordinary RFQ push path (self-scoped to RFQs a taker created on that socket), `cashout:offer`/`cashout:executed` concern a position the subscriber may not have created in the current session. These pushes are gated behind the signed `hello` variant (§4.2) specifically to prevent one taker from snooping on another address's cashout activity by simply sending an unsigned `hello` for a victim's address.

### Standing allowance security

Makers typically grant the escrow a standing EIP-2612 allowance once (via the `approval` field on `auth`) rather than signing a fresh permit per quote. This grants the escrow a large, long-lived spend authority from the maker's address. Use a dedicated hot wallet for the maker role, monitor its balance and on-chain allowance, and revoke on-chain directly if you exit the relay. See [Maker Protocol Spec §8, Standing allowance](/mute.sh/reference/maker-protocol-spec.md#8-standing-allowance-eip-2612) for the full mechanics.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://mute-sh.gitbook.io/mute.sh/reference/ws-events.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
