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

# WebSocket

> Real-time trade confirmations and position updates via WebSocket.

The Totalis WebSocket pushes post-trade events live: quote acceptances, confirmations, and position
settlements. You connect, authenticate, subscribe to channels, and handle events as they arrive.

For pre-trade pricing, use the SSE streams: the [maker stream](/solana/api-reference/quote-service-mm/stream)
for incoming quote requests, and the [quote-request stream](/solana/api-reference/quote-service/stream) for
the best current quote.

<Note>
  Events reach you only while you are connected. Missed events are not redelivered. For durable
  delivery to a server, use [webhooks](/solana/guides/webhooks).
</Note>

<img className="block dark:hidden rounded-2xl" src="https://mintcdn.com/ucalledit/YdiR6y3UIknGsFJp/images/pictures/websocket-light.png?fit=max&auto=format&n=YdiR6y3UIknGsFJp&q=85&s=6ae6f2cf761467fcc45aad46f04c151f" alt="Connect, authenticate before the timeout, subscribe to your channel, and events arrive while pings keep it open" width="1540" height="952" data-path="images/pictures/websocket-light.png" />

<img className="hidden dark:block rounded-2xl" src="https://mintcdn.com/ucalledit/YdiR6y3UIknGsFJp/images/pictures/websocket-dark.png?fit=max&auto=format&n=YdiR6y3UIknGsFJp&q=85&s=eb7e936d53c2f267daabb94b0dad1b5f" alt="Connect, authenticate before the timeout, subscribe to your channel, and events arrive while pings keep it open" width="1540" height="952" data-path="images/pictures/websocket-dark.png" />

## Connecting

```
wss://api.totalis.trade/ws
```

## Authentication

Send an auth message with your API key as soon as you connect:

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

The server answers with one of:

<CodeGroup>
  ```json Success theme={null}
  { "type": "auth:success", "user_id": "did:privy:abc123", "user_type": "unified" }
  ```

  ```json Error theme={null}
  { "type": "auth:error", "code": "INVALID_CREDENTIALS", "message": "Invalid API key" }
  ```
</CodeGroup>

Every `auth:error` carries a `code`. Branch on `code`, not on `message`, which is not stable.

| `code` | Meaning | Retry |
| - | - | - |
| `INVALID_CREDENTIALS` | The key is missing, malformed, invalid, or you are already authenticated. | No. Fix the credential. |
| `TOKEN_INVALID` | A Privy JWT failed verification. | No |
| `AUTH_TIMEOUT` | You connected but sent no auth message within 10 seconds. The socket closes with code `4001`. | Yes. Reconnect and authenticate. |
| `SERVICE_UNAVAILABLE` | Authentication could not be completed on our side. | Yes, with backoff |

## Subscribing to channels

After you authenticate, subscribe to each channel you want:

```json theme={null}
{ "type": "subscribe", "channel": "mm:quotes:<your-mm-id>" }
```

| Channel | Description |
| - | - |
| `mm:quotes:{mm_id}` | Your quote lifecycle updates (accepted, accepted details, confirmed, expired, withdrawn) |
| `rfq:{rfq_id}` | Status changes, cancellation, expiry and position events for one trade |

Your **`mm_id`** is the same value as your `user_id`: your Privy DID (`did:privy:...`). `auth:success`
returns it, and so does the [`connected`](/solana/api-reference/quote-service-mm/stream#connected) event on
the MM SSE stream.

## Events

### Quote events

| Event | Description | Data |
| - | - | - |
| `quote:accepted` | A user committed your quote. Confirm it before `confirmation_deadline`. The default is 5 s from acceptance but it is configurable, so always honor the timestamp. | `{ quote_id, rfq_id, confirmation_deadline }` |
| `mm_quote:accepted` | Accepted quote details, sent only to the selected market maker. Use it to reserve local exposure or record analytics immediately. Its `quote_id` is valid for confirmation. Do not depend on its order relative to `quote:accepted`. | `{ rfq_id, quote_id, user_id, quote, exposure_legs, confirmation_deadline, accepted_at }` |
| `quote:confirmed` | You confirmed the quote. Vault settlement begins. | `{ quote_id, rfq_id, execution_id }` |
| `quote:executed` | On-chain vault position created. | `{ quote_id, rfq_id }` |
| `quote:expired` | Quote expired before confirmation. | `{ quote_id, rfq_id }` |
| `quote:rejected` | Another quote was accepted instead. | `{ quote_id, rfq_id, reason }` |
| `quote:withdrawn` | You withdrew the quote. | `{ quote_id, rfq_id }` |

`mm_quote:accepted` payload:

```json theme={null}
{
  "rfq_id": "550e8400-e29b-41d4-a716-446655440000",
  "quote_id": "7d3a2f1e-4b8c-4d6e-9f0a-1b2c3d4e5f60",
  "market_maker_id": "did:privy:cm3mm...",
  "user_id": "did:privy:cm3abc...",
  "source": "quote_request_commit",
  "quote_request_id": "1a6d1f06-9d4f-47cb-994b-3bdfbbef7e40",
  "draft_quote_id": "b2c3d4e5-6789-0abc-def1-234567890abc",
  "request_version": 3,
  "request_hash": "sha256:...",
  "confirmation_deadline": "2026-06-05T12:00:05.000Z",
  "accepted_at": "2026-06-05T12:00:00.000Z",
  "quote": {
    "payout_odds": 7.08,
    "user_cost": 49.50,
    "total_payout": 350.46,
    "mm_cost": 300.96,
    "valid_until": "2026-06-05T12:00:10.000Z"
  },
  "exposure_legs": [
    {
      "venue": "kalshi",
      "event_ticker": "KXCAR",
      "market_ticker": "KXCAR-YES",
      "side": "yes"
    }
  ]
}
```

<Note>
  `draft_quote_id` is the `quote.id` you got back when you
  [submitted your draft quote](/solana/api-reference/quote-service-mm/submit-quote). Use it to see which of
  your drafts was lifted. Use `quote_id`, the accepted quote, for the
  [confirmation call](/solana/api-reference/mm/confirm-quote).
</Note>

### Parlay events

Delivered on `rfq:{rfq_id}`. These are the user-facing mirror of the maker's `quote:*` events.

| Event | Description | Data |
| - | - | - |
| `rfq:status` | The parlay moved to a new status: `accepted`, `confirmed`, `executed` or `cancelled`. Same moments as the `parlay.status_changed` webhook. | `{ rfq_id, status, updated_at, accepted_quote_id? }` |
| `rfq:cancelled` | The parlay was cancelled. | `{ rfq_id, reason? }` |
| `rfq:expired` | The parlay expired before it was funded. | `{ rfq_id }` |

### Position events

`position_pda` is the position's on-chain account address, not a lookup key. To fetch or reference
a position through the API, use `rfq_id`, or `position_id` once it is
[set on the RFQ](/solana/api-reference/parlays/get).

| Event | Description | Data |
| - | - | - |
| `position:created` | Vault position created, collateral locked. | `{ rfq_id, position_pda, timestamp }` |
| `position:settled` | Position settled with outcome. | `{ rfq_id, position_pda, outcome, tx_signature, timestamp }` |
| `position:cancelled` | Position cancelled, collateral unlocked. | `{ rfq_id, position_pda, reason, timestamp }` |
| `position:failed` | One attempt to create the position on chain failed and will be retried. Delivered on `rfq:{rfq_id}`, to the user, and to the maker. | `{ rfq_id, reason, retry_count, timestamp }` |
| `position:error` | Position execution error. | `{ rfq_id, error, timestamp }` |
| `position:bought_back` | A winning cashout bid closed the position early. See the note below. | `{ rfq_id, position_pda, amount, mm_pays_user, tx_signature, quote_id, timestamp }` |

<Note>
  `position:bought_back` fans out to the `rfq:{rfq_id}` channel, to the `mm:quotes:{mm_id}` channel of
  the maker whose exposure closed, and directly to any connection authenticated as the seller or as
  that maker. Subscribe on `rfq:{rfq_id}` and dedupe on `tx_signature`.

  `amount` is decimal USDC. `quote_id` is the position's original accepted quote, not the winning bid.
  For bids that acquire a position instead, see
  [the acquisition gap](/solana/guides/event-catalog#the-acquisition-gap).
</Note>

### Balance events

Delivered to every connection authenticated as you, with no subscription. Amounts are decimal USDC.

| Event | Description | Data |
| - | - | - |
| `vault:deposited` | USDC moved into your vault, whether by a trade or by `POST /v1/vault/deposit`. | `{ amount, vault_balance, free_balance }` |
| `vault:withdrawn` | USDC moved out of your vault. | `{ amount, vault_balance, free_balance }` |

## Keep-alive

Send periodic pings to keep the connection open:

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

The server replies:

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

## Example

```javascript theme={null}
const ws = new WebSocket('wss://api.totalis.trade/ws');

ws.onopen = () => {
  ws.send(JSON.stringify({ type: 'auth', api_key: '<your-api-key>' }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  if (msg.type === 'auth:success') {
    // Your mm_id is your user_id (Privy DID)
    ws.send(JSON.stringify({
      type: 'subscribe',
      channel: `mm:quotes:${msg.user_id}`
    }));
  }

  if (msg.type === 'quote:accepted') {
    console.log('Quote accepted! Confirm before:', msg.data.confirmation_deadline);
    // POST https://api.totalis.trade/v1/mm/quotes/{quoteId}/confirm
  }

  if (msg.type === 'mm_quote:accepted') {
    console.log('Accepted quote details:', msg.data.exposure_legs);
  }

  if (msg.type === 'position:settled') {
    console.log('Position settled:', msg.data.outcome);
  }
};

setInterval(() => {
  ws.send(JSON.stringify({ type: 'ping' }));
}, 30_000);
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.