> ## Documentation Index
> Fetch the complete documentation index at: https://docs.convallax.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Post-Trade WebSocket

> WebSocket message reference for the Convallax post-trade channel — connection, authentication, and the quote:accepted / quote:confirmed / quote:rejected events that drive maker settlement.

Convallax splits maker transport into **three channels**. The WebSocket is **channel 3** — the **post-trade** channel. It is a server → maker push connection that delivers a single thing: notification that one of your quotes has won (or lost) once a taker commits. It no longer carries quote requests or quote submissions.

| Channel              | Transport     | Direction      | Purpose                                                                            |
| -------------------- | ------------- | -------------- | ---------------------------------------------------------------------------------- |
| 1 — Quote requests   | SSE           | server → maker | Receive live `quote_request` events ([Quote Request Stream](/guides/quote-stream)) |
| 2 — Quote submission | REST          | maker → server | `POST /v1/mm/quotes` and confirm signature                                         |
| 3 — Post-trade       | **WebSocket** | server → maker | `quote:accepted` / `quote:confirmed` / `quote:rejected` (this page)                |

<Info>
  A maker must be connected to **both** the [SSE quote-request stream](/guides/quote-stream) (to receive requests) **and** this WebSocket (to receive `quote:accepted`) to fully participate. Confirmation signatures go back over REST (channel 2), **not** over the WebSocket.
</Info>

Convallax remains **non-custodial**: makers sign their own EIP-712 settlement orders with their own wallet key. This transport split changes only *how messages move*, not the trust model.

## Connecting

```
wss://api.convallax.com/maker/v1/ws
```

## Authentication

Each market maker authenticates with a **per-MM API key** generated from the [dashboard](https://convallax.com/settings). The key maps to a stable `makerId`. There are two equivalent ways to authenticate:

**Option A — query parameter:**

```
wss://api.convallax.com/maker/v1/ws?apiKey=<your-key>
```

**Option B — `auth` message** sent immediately after the socket opens:

```json theme={null}
{
  "type": "auth",
  "apiKey": "<your-key>"
}
```

On successful authentication, the server sends a confirmation that echoes your resolved `makerId`:

```json theme={null}
{
  "type": "connected",
  "protocolVersion": 3,
  "makerId": "mm-alpha",
  "authenticated": true,
  "serverTime": "2026-06-15T12:00:00.000Z"
}
```

If the API key is missing or invalid (when the relay is configured with keys), the connection is closed with code `4001`.

<Info>
  If the relay is started without any maker keys configured, it runs in **OPEN dev mode** — it accepts anyone and assigns an anonymous `makerId`. See the [Authentication guide](/guides/authentication) for full details and how to obtain a key.
</Info>

<Note>
  The post-trade WebSocket still authenticates via `?apiKey=` or the `auth` message. The SSE quote-request stream and the REST quote endpoints authenticate via the `X-API-Key` header instead (see [Authentication](/guides/authentication)).
</Note>

## Keep-Alive

The server sends a WebSocket **ping** every \~25 seconds; your client must respond with a **pong** to maintain the connection (most WebSocket libraries handle this automatically). You can also send an application-level keep-alive at any time:

```json theme={null}
{ "type": "ping" }
```

The relay replies with:

```json theme={null}
{ "type": "pong", "timestamp": "2026-06-15T12:00:00.000Z" }
```

## Post-Trade Events

The WebSocket delivers **only** post-trade events. There is no `quote_request`, `quote`, `order_to_sign`, or `order_signed` on this channel anymore — those moved to SSE (in) and REST (out).

### `quote:accepted` — You won; sign and confirm

Sent **only to the winning maker** when a taker commits. It carries the exact `Order` struct, the EIP-712 `domain` and `types`, and a `confirmationDeadline`. Sign the `order` with the wallet key for `order.maker`, then **POST the signature** to [`POST /v1/mm/quotes/:quoteId/confirm`](/api-reference/market-maker/confirm-quote) **before** `confirmationDeadline`.

```json theme={null}
{
  "type": "quote:accepted",
  "quoteId": "8f3c2b1a-...",
  "requestId": "req-789",
  "order": {
    "maker": "0xYourMakerWallet...",
    "seriesId": "98765432109876543210",
    "optionAmount": "10000000",
    "premiumAmount": "1200000",
    "makerSelling": true,
    "taker": "0xTrader...",
    "validUntil": 1717189320,
    "nonce": 1717189200042
  },
  "domain": {
    "name": "ConvallaxRFQSettlement",
    "version": "1",
    "chainId": 80002,
    "verifyingContract": "0x3E583BC44157c91F8c534372A3e91c371841107A"
  },
  "types": {
    "Order": [
      { "name": "maker", "type": "address" },
      { "name": "seriesId", "type": "uint256" },
      { "name": "optionAmount", "type": "uint256" },
      { "name": "premiumAmount", "type": "uint256" },
      { "name": "makerSelling", "type": "bool" },
      { "name": "taker", "type": "address" },
      { "name": "validUntil", "type": "uint256" },
      { "name": "nonce", "type": "uint256" }
    ]
  },
  "confirmationDeadline": "2026-06-15T12:00:11.000Z"
}
```

The `order` / `domain` / `types` shapes are identical to the legacy `order_to_sign` message — only the transport changed. The backend verifies that your signature recovers to `order.maker`.

<Warning>
  If you miss the `confirmationDeadline`, the backend falls back to the next-best maker's quote. Set your USDC approvals (Core for collateral, Settlement for premium) **before** quoting so you can sign instantly — collateral is locked only at fill, so there is no inventory to pre-position.
</Warning>

### `quote:confirmed` — Your quote won and was confirmed

Sent after your confirmation signature is verified. Your trade is locked in and the taker can now settle on-chain.

```json theme={null}
{
  "type": "quote:confirmed",
  "requestId": "req-789",
  "quoteId": "8f3c2b1a-..."
}
```

### `quote:rejected` — Another maker won (or the request closed)

Sent when another maker's quote won, or when the request closed without your quote winning. Drop the request from local state.

```json theme={null}
{
  "type": "quote:rejected",
  "requestId": "req-789",
  "quoteId": "8f3c2b1a-...",
  "reason": "another_quote_won"
}
```

## Protocol Version

The current protocol version is **3**. The `protocolVersion` field in the `connected` message tells you which version the relay is running. Check the version on connect to ensure compatibility.

<Info>
  **Migrating from protocol v2:** The WebSocket no longer carries `quote_request`, `quote`, `order_to_sign`, or `order_signed`. Quote requests now arrive over the [SSE quote-request stream](/guides/quote-stream); quotes and confirmation signatures are submitted over [REST](/api-reference/market-maker/submit-quote). The WebSocket is now post-trade only, delivering `quote:accepted`, `quote:confirmed`, and `quote:rejected`.
</Info>

## Example: Full Maker Lifecycle Across All Three Channels

This example subscribes to the SSE quote-request stream for incoming requests, POSTs quotes via REST, listens on the WebSocket for `quote:accepted`, signs with `ethers`, and POSTs the signature to the confirm endpoint.

```javascript theme={null}
import EventSource from 'eventsource';
import WebSocket from 'ws';
import { Wallet } from 'ethers';

const API = 'https://api.convallax.com';
const WS = 'wss://api.convallax.com';
const API_KEY = process.env.MAKER_API_KEY;
const wallet = new Wallet(process.env.MM_PRIVATE_KEY);

// quoteId is server-generated; remember it per request so we can sign on accept.
const quoteIds = new Map(); // requestId -> quoteId

// --- Channel 1: SSE quote-request stream (server -> maker) ---
const stream = new EventSource(`${API}/v1/mm/quote-requests/stream`, {
  headers: { 'X-API-Key': API_KEY },
});

stream.addEventListener('connected', (e) =>
  console.log('SSE connected', JSON.parse(e.data))
);

stream.addEventListener('quote_request', async (e) => {
  const req = JSON.parse(e.data);
  const { option, trade } = req.params;
  const price = computePrice(option, trade); // your pricing model

  // --- Channel 2: submit the quote over REST ---
  const res = await fetch(`${API}/v1/mm/quotes`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY },
    body: JSON.stringify({
      requestId: req.requestId,
      quote: {
        maker: wallet.address, // MUST be your own wallet
        side: trade.side,      // must match the taker's side
        price,
        size: trade.size,
      },
    }),
  });
  const { quoteId } = await res.json();
  quoteIds.set(req.requestId, quoteId); // server-generated, stable across updates
});

stream.addEventListener('quote_request_expired', (e) => {
  const { requestId } = JSON.parse(e.data);
  quoteIds.delete(requestId);
});

// --- Channel 3: post-trade WebSocket (server -> maker) ---
const ws = new WebSocket(`${WS}/maker/v1/ws?apiKey=${API_KEY}`);
ws.on('ping', () => ws.pong());

ws.on('message', async (raw) => {
  const msg = JSON.parse(raw);

  switch (msg.type) {
    case 'connected':
      console.log(`WS v${msg.protocolVersion} as ${msg.makerId}`);
      break;

    case 'quote:accepted': {
      // You won — sign the order and confirm over REST before the deadline.
      const signature = await wallet.signTypedData(msg.domain, msg.types, msg.order);
      await fetch(`${API}/v1/mm/quotes/${msg.quoteId}/confirm`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY },
        body: JSON.stringify({ signature }),
      });
      break;
    }

    case 'quote:confirmed':
      console.log(`Confirmed ${msg.quoteId} for ${msg.requestId}`);
      break;

    case 'quote:rejected':
      console.log(`Rejected ${msg.quoteId}: ${msg.reason}`);
      break;
  }
});
```
