# WebSocket Events

> **SDK note:** The `@dimes-dot-fi/sdk` package does not include a WebSocket client. Use Socket.IO directly alongside the SDK for real-time updates. The REST hooks (`usePositions`, `useMarkets`) handle polling; use WebSocket events to trigger cache invalidation for instant updates.

Two WebSocket gateways are available. Both deliver the same event types and payload shapes — they differ only in authentication and scope.

| Gateway  | Namespace                                      | Auth     | Scope                                         |
| -------- | ---------------------------------------------- | -------- | --------------------------------------------- |
| Partner  | `/v1/ws/prediction-markets/positions`          | API key  | All positions for the partner                 |
| Customer | `/v1/ws/prediction-markets/customer-positions` | User JWT | Only positions for the authenticated customer |

***

### Partner gateway

**Namespace:**

| Environment | WebSocket URL                                                   |
| ----------- | --------------------------------------------------------------- |
| Production  | `wss://api.dimes.fi/v1/ws/prediction-markets/positions`         |
| Sandbox     | `wss://api-sandbox.dimes.fi/v1/ws/prediction-markets/positions` |

Sandbox emits the same event types and payload shapes as production — only the host and the key prefix differ. See [Environments](/for-developers/environments.md).

**Transport:** Socket.IO

**Auth:** Partner API key, passed as either:

* **Socket.IO `auth` payload (recommended):** `auth: { apiKey: "dm_live_skey_..." }` (or `dm_sbx_skey_...` in sandbox). Transmitted in the handshake body — never appears in URLs, server logs, or browser history.
* **HTTP header:** `Authorization: Api-Key dm_live_skey_...` (or `dm_sbx_skey_...` in sandbox). For Socket.IO clients, set this via the `extraHeaders` option.

On successful auth, the connection is scoped to the partner. All events for positions created through that partner's API keys are delivered.

```javascript
import {io} from "socket.io-client";

const socket = io("wss://api.dimes.fi/v1/ws/prediction-markets/positions", {
  auth: {apiKey: process.env.DIMES_API_KEY},
});

socket.on("connect", () => {
  console.log("Connected to position events");
});

socket.on("position.opened", (event) => {
  console.log("Position opened:", event.data.id);
});

socket.on("error", (error) => {
  console.error("WebSocket error:", error);
});
```

***

### Customer gateway

| Environment | WebSocket URL                                                            |
| ----------- | ------------------------------------------------------------------------ |
| Production  | `wss://api.dimes.fi/v1/ws/prediction-markets/customer-positions`         |
| Sandbox     | `wss://api-sandbox.dimes.fi/v1/ws/prediction-markets/customer-positions` |

**Transport:** Socket.IO

**Auth:** User JWT (the same token from `POST /tokens`), passed as either:

* **Socket.IO `auth` payload (recommended):** `auth: { token: "<jwt>" }`.
* **HTTP header:** `Authorization: Bearer <jwt>`. For Socket.IO clients, set this via the `extraHeaders` option.

On successful auth, the connection is scoped to the authenticated customer. Only events for positions belonging to that specific wallet address and partner are delivered — other customers' positions are never visible.

```javascript
import {io} from "socket.io-client";

const socket = io("wss://api.dimes.fi/v1/ws/prediction-markets/customer-positions", {
  auth: {token: customerJwt},
});

socket.on("position.opening", (event) => {
  // Early signal — position is being finalized on-chain
  showFinalizing(event.data.id);
});

socket.on("position.opened", (event) => {
  // Position fully open — display position card
  addOpenPosition(event.data);
});
```

***

### Authentication errors

If authentication fails on either gateway, the server emits an `error` event and disconnects:

```json
{
  "error": {
    "type": "AUTHENTICATION_ERROR",
    "code": "unauthorized",
    "message": "Unauthorized"
  }
}
```

***

### Event envelope

All events follow the `resource.action` naming pattern with a consistent envelope:

```json
{
  "id": "evt_abc123",
  "type": "position.opened",
  "created_at": "2025-06-01T10:00:05.000Z",
  "data": {
    ...
  }
}
```

| Field        | Type              | Description                                                                          |
| ------------ | ----------------- | ------------------------------------------------------------------------------------ |
| `id`         | string            | Unique event identifier (prefixed `evt_`)                                            |
| `type`       | string            | Event type in `resource.action` format                                               |
| `created_at` | string (ISO 8601) | When the event was emitted                                                           |
| `data`       | object            | Event payload — always contains the full position object matching the REST API shape |

The `data` object contains the same representation you would get from `GET /positions/{id}`. This means event consumers can reuse the same types/parsers as their REST integration.

***

### Event types

| Event Type                 | Trigger                                                                     |
| -------------------------- | --------------------------------------------------------------------------- |
| `position.created`         | On-chain position account created (user submitted tx)                       |
| `position.opening`         | Finalize-open tx confirmed on-chain (early signal, before indexer picks up) |
| `position.opened`          | Position finalized on-chain, tokens acquired (indexer confirmed)            |
| `position.close_requested` | User submitted close request                                                |
| `position.closed`          | Close finalized, proceeds distributed                                       |
| `position.settled`         | Market resolved, position settled                                           |
| `position.liquidated`      | Position liquidated due to insufficient margin                              |
| `position.force_unwound`   | Position partially unwound to reduce leverage (position remains open)       |
| `position.reverted`        | Position open failed, collateral refunded                                   |
| `position.cancelled`       | Position cancelled before opening (EVM only)                                |

***

### Event payloads

The `data` field in every event is a full position object — the same shape returned by the REST API. See the [Positions](https://docs.dimes.fi/for-developers/pages/NdOdqtZSHifVN4PbuN3l#4.-positions-user-jwt) section of the API Reference for complete field definitions.

| Event                      | `status`     | Position shape                                                         |
| -------------------------- | ------------ | ---------------------------------------------------------------------- |
| `position.created`         | `pending`    | Open shape (`entry`, `current`, `risk`, `fees`, `timing`)              |
| `position.opening`         | `pending`    | Open shape (early signal — position not yet indexed)                   |
| `position.opened`          | `open`       | Open shape                                                             |
| `position.close_requested` | `closing`    | Open shape                                                             |
| `position.closed`          | `closed`     | Closed shape — `close_reason: "closed"`, `entry`, `result`, `fees`     |
| `position.settled`         | `settled`    | Closed shape — `close_reason: "settled"`, `entry`, `result`, `fees`    |
| `position.liquidated`      | `liquidated` | Closed shape — `close_reason: "liquidated"`, `entry`, `result`, `fees` |
| `position.force_unwound`   | `open`       | Open shape (reduced size — `current` values reflect new position)      |
| `position.reverted`        | `cancelled`  | Closed shape — `close_reason: "reverted"`, `entry`, `result`, `fees`   |
| `position.cancelled`       | `cancelled`  | Closed shape — `close_reason: "reverted"`, `entry`, `result`, `fees`   |

Open positions include `current`, `risk`, `fees`, and `timing` sections. Closed, settled, and liquidated positions replace those with `close_reason` and `result` (including `collected_liquidation_fee_*` for liquidations).

#### `position.opening` vs `position.opened`

`position.opening` fires as soon as the finalize-open transaction is confirmed on-chain — **before** the event indexer picks it up. This gives the UI an early signal (\~5-15 seconds faster) that the position is about to open.

`position.opened` fires later when the indexer delivers the confirmed `PositionOpened` event.

**Recommended UI behavior:** On `position.opening`, show a "finalizing" state. On `position.opened`, transition to the full open position view with live data. If `position.reverted` arrives instead, the open failed — show the error state.

***

### Listening to events

Subscribe to specific event types using Socket.IO's standard event listeners:

```javascript
socket.on("position.created", (event) => {
  updatePositionStatus(event.data.id, "pending");
});

socket.on("position.opening", (event) => {
  // Early signal — show "finalizing" state
  updatePositionStatus(event.data.id, "finalizing");
});

socket.on("position.opened", (event) => {
  addOpenPosition(event.data);
});

socket.on("position.close_requested", (event) => {
  updatePositionStatus(event.data.id, "closing");
});

socket.on("position.closed", (event) => {
  showSettlement(event.data);
});

socket.on("position.settled", (event) => {
  showSettlement(event.data);
});

socket.on("position.liquidated", (event) => {
  alertLiquidation(event.data);
});

socket.on("position.force_unwound", (event) => {
  // Position still open but reduced in size
  updateOpenPosition(event.data);
});

socket.on("position.reverted", (event) => {
  // Open failed — show error
  showOpenFailed(event.data);
});

socket.on("position.cancelled", (event) => {
  showCancelled(event.data);
});
```

***

### Notification events (customer gateway only)

In addition to position state-transition events, the customer gateway emits `notification` events. These are informational messages that provide feedback during asynchronous operations — they are not errors and do not indicate a state change.

**Envelope:**

```json
{
  "id": "evt_abc123",
  "type": "notification",
  "created_at": "2025-06-01T10:00:05.000Z",
  "data": {
    "code": "ORDER_FULFILLMENT_RETRYING",
    "message": "Order fulfillment not possible at current slippage tolerance. Retrying.",
    "params": {
      "position_id": "dm_pos_abc123",
      "slippage_bps": 150
    }
  }
}
```

| Field          | Type   | Description                                      |
| -------------- | ------ | ------------------------------------------------ |
| `data.code`    | string | Machine-readable notification code               |
| `data.message` | string | Human-readable description                       |
| `data.params`  | object | Additional context (varies by notification code) |

**Notification codes:**

| Code                         | When                                                                            | Params                                                                                     |
| ---------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `ORDER_FULFILLMENT_RETRYING` | Order could not be filled at current slippage; retrying                         | `position_id`, `slippage_bps`                                                              |
| `PARTIAL_OPEN_PROGRESS`      | Floor or retry leg of a FAK partial-open fulfilled; reports cumulative progress | `position_id`, `cumulative_filled_making_amount`, `target_making_amount`, `attempt_number` |
| `PARTIAL_OPEN_FLOOR_MISSED`  | Floor FOK of a FAK partial-open failed to fill after retries; position reverted | `position_id`                                                                              |

**Listening:**

```javascript
socket.on("notification", (event) => {
  showToast(event.data.message);
});
```

Notifications are transient — display them as a toast or inline status message. They do not require user action.

***

### Best practices

**Deduplicate on event `id`.** Store processed event IDs and skip duplicates. The same event may be delivered more than once.

**Handle events out of order.** A `position.closed` event could arrive before `position.close_requested` due to network timing. Use the `created_at` timestamp and the position status from the REST API as the source of truth if ordering matters.

**Reconnect on disconnect.** Configure your client's reconnection settings for production reliability.

**Use REST API as fallback.** If your WebSocket connection drops, use `GET /positions` to catch up on any missed state changes.


---

# Agent Instructions: 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:

```
GET https://docs.dimes.fi/for-developers/websocket.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
