For the complete documentation index, see llms.txt. This page is also available as Markdown.

WebSocket Events

Receive real-time position updates via WebSocket. All position state changes are delivered as events over a persistent Socket.IO connection.

SDK support: The @dimes-dot-fi/sdk/ws entry point ships PositionSocket and MarketSocket clients for the user gateways (JWT auth). They wrap Socket.IO, manage reconnection, and camelize every payload to match the rest of the SDK. Prefer them over wiring up Socket.IO by hand — see Using the SDK below. For the partner gateways (API key auth) the SDK has no client yet; use Socket.IO directly as shown in the raw examples. Runnable: 05-websocket.ts (Node) and react/streams.tsx (React reconciliation).

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

User

/v1/ws/prediction-markets/user-positions

User JWT

Only positions for the authenticated user


Using the SDK

For the user gateways, the SDK's @dimes-dot-fi/sdk/ws entry point gives you typed clients so you don't touch Socket.IO directly. Both clients take a getToken callback (so they always reconnect with a fresh JWT) and an optional baseUrl (defaults to https://api.dimes.fi — set it to https://api-sandbox.dimes.fi for sandbox). Payloads arrive camelCased (event.createdAt, event.data) to match the REST types returned elsewhere by the SDK.

Position events

PositionSocket connects to the user-positions gateway. Subscribe to a specific event type, to "*" for all of them, or to onNotification for the user-gateway notification stream. Every on* call returns an unsubscribe function.

import { PositionSocket } from "@dimes-dot-fi/sdk/ws";

const socket = new PositionSocket({
  getToken: () => currentJwt, // called on every (re)connect
  // baseUrl: "https://api-sandbox.dimes.fi", // for sandbox
});

socket.onConnect(() => console.log("connected"));

const off = socket.on("position.opened", (event) => {
  // event.data is a full Position (camelCased), same shape as the REST API
  addOpenPosition(event.data);
});

socket.on("position.opening", (event) => showFinalizing(event.data.id));

// Transient, informational messages (user gateway only)
socket.onNotification((event) => showToast(event.data.message));

socket.onError((err) => console.error("ws error", err));

socket.connect();

// later: off(); socket.disconnect();

When the JWT rotates, call socket.reconnectWithToken() to drop and re-establish the connection with the new token.

Market events

MarketSocket connects to the user-markets gateway with the same API. Remember that market event data is always an array (see Market events below):

The raw Socket.IO examples below remain accurate for the partner gateways and non-JS clients; the SDK clients simply wrap that same protocol for the user gateways.


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.

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.


User gateway

Environment
WebSocket URL

Production

wss://api.dimes.fi/v1/ws/prediction-markets/user-positions

Sandbox

wss://api-sandbox.dimes.fi/v1/ws/prediction-markets/user-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 user. Only events for positions belonging to that specific wallet address and partner are delivered — other users' positions are never visible.


Authentication errors

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


Event envelope

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

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)

position.settlement_state_changed

The position's market moved to a new settlement stage (e.g. the market closed and is now awaiting resolution). The position itself does not change state


Event payloads

The data field in every event is a full position object — the same shape returned by the REST API. See the Positions 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", revert_reason, entry, result, fees

position.cancelled

cancelled

Closed shape — close_reason: "reverted", entry, result, fees

position.settlement_state_changed

unchanged

Open shape — read timing.settlement_state and timing.market_status

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).

The position.reverted payload additionally carries revert_reason, classifying why the open failed before it landed: exchange_unavailable (the prediction-market venue was temporarily unavailable — safe to retry), slippage_exceeded (price moved beyond your slippage tolerance before the order filled), or unknown. It is non-null only when close_reason is reverted; collateral is always returned regardless of reason. Surface exchange_unavailable as a transient, retryable issue and slippage_exceeded as a price-moved message.

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:


Notification events (user gateway only)

In addition to position state-transition events, the user 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:

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:

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


Market events

In addition to position events, two WebSocket gateways stream market updates: new markets coming online, eligibility changes, and max-leverage changes. They mirror the position gateways exactly — same Socket.IO transport, same auth, same envelope — and differ only in scope.

Gateway
Namespace
Auth
Scope

Partner

/v1/ws/prediction-markets/markets

API key

All markets visible to the partner

User

/v1/ws/prediction-markets/user-markets

User JWT

Markets visible to the authenticated user

Auth is identical to the position gateways: the partner gateway takes an API key (auth: { apiKey: "dm_live_skey_..." } or Authorization: Api-Key ...), and the user gateway takes a user JWT (auth: { token: "<jwt>" } or Authorization: Bearer <jwt>). See Partner gateway and User gateway above for the full handshake details — they apply unchanged.

Environment
Partner URL
User URL

Production

wss://api.dimes.fi/v1/ws/prediction-markets/markets

wss://api.dimes.fi/v1/ws/prediction-markets/user-markets

Sandbox

wss://api-sandbox.dimes.fi/v1/ws/prediction-markets/markets

wss://api-sandbox.dimes.fi/v1/ws/prediction-markets/user-markets

data is always an array

Important: Unlike position events — where data is a single object — market events always deliver data as an array. A single market change arrives as an array of one; when many markets change at once (e.g. a batch eligibility sweep), they are batched into a single event. Always iterate data as an array.

The envelope is otherwise identical to position events:

Market event types

Event Type

Trigger

data items

market.discovered

A new market comes online

Full market objects (same shape as GET /v1/markets)

market.eligibility_changed

A market's tradeability / capacity / notional limits change

Delta objects — only the changed fields

market.max_leverage_changed

An eligible market's max leverage changes

Delta objects — id, polymarket, leverage (no min_bps/step_bps)

market.discovered carries the complete market payload — identical to an item from GET /v1/markets, including the optional prices block when available. The two *_changed events carry deltas: each item contains the market id, the always-present polymarket identifiers (so you can map the delta onto your cached market), and only the fields that actually changed. Merge those fields into your cached copy of the market by id.

market.discovered

data is an array of full market objects, each identical to an item from GET /v1/markets. See the Markets section of the API Reference for complete field definitions.

market.eligibility_changed

data is an array of deltas. Each delta is { id, polymarket, ...only the changed fields }. Changed fields may include any of: accepting_new_positions, rejection_reason_code, sided_eligibility, max_notional_yes_usd(_pips), max_notional_no_usd(_pips), capacity_max_notional_yes_usd(_pips), capacity_max_notional_no_usd(_pips), slippage_max_notional_yes_usd(_pips), slippage_max_notional_no_usd(_pips). It never includes leverage.

market.max_leverage_changed

data is an array of deltas. Each delta is { id, polymarket, leverage }, where leverage carries the same fields as the leverage block in a full market object except the constants min_bps and step_bps — those never change, so read them from the market discovery payload. Only emitted for markets that are currently allowed/eligible.

Connecting to market events

The connection flow is identical to the position gateways — only the namespace changes. Remember to iterate data:

For the partner gateway, swap the namespace to /v1/ws/prediction-markets/markets and authenticate with your API key (auth: {apiKey: process.env.DIMES_API_KEY}) exactly as shown for the partner position gateway.


Testing your integration

Waiting for real positions to move through their lifecycle is a slow way to build a UI. POST /sample-events emits one of every event type onto your own WebSocket connection, one per second, with fixed mock payloads — so you can build and debug every branch of your event handling in under a minute.

Endpoint

POST /v1/prediction-markets/sample-events

Auth

Partner API key or user JWT

Rate limit

One request every 30 seconds

Availability

All environments, including production

Which gateway receives the events depends on how you authenticate:

  • Partner API key → the events land on the partner positions gateway (/v1/ws/prediction-markets/positions), scoped to your partner. You get every position event type.

  • User JWT → the events land on the user positions gateway (/v1/ws/prediction-markets/user-positions), scoped to the wallet in the token. You get every position event type plus one notification event for every notification code.

Only your own connection receives these events. Nobody else's clients see it.

Nothing is created or persisted. The payloads are canned mock data: every one carries the position ID dm_pos_sampleevent000000 and the ticker dimes-sample-market, neither of which exists. Fetching them over REST will 404 — that is expected. Filter sample events out of production analytics by checking for that position ID if you need to.

Connect first, then call

The events are fired at whatever is connected at that moment. Open your WebSocket, wait for it to authenticate, and only then call the endpoint.

The call returns 202 Accepted immediately with the size and pacing of what is about to arrive:

The position events arrive first, then the notification events. Read event_count to know how many messages to expect and interval_ms for the spacing — do not hard-code either. New event types are added to the sample set as they are added to the protocol, so a handler that copes with everything listed under Event types will not be surprised later.

A worked example

What the sample events do not cover

  • Market events. market.discovered, market.eligibility_changed and market.max_leverage_changed are global broadcasts, so they are deliberately excluded. Sandbox produces real market events at a steady rate — use it to exercise that path.

  • Ordering and timing realism. The endpoint walks the whole catalogue in a fixed order in one pass. Real positions emit a handful of these, with gaps of seconds to days between them, and occasionally out of order.

  • Error paths. Authentication failures, disconnects and reconnection are not simulated. Test those by dropping the connection yourself.


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.

Never assume you know every event type. New event types are added over time and will start arriving on your existing connection without warning. Route on the type field with an explicit default branch that logs and ignores anything unrecognised — never one that throws or drops the connection.

Treat data.status as the truth, not the event name. Several events leave a position open (position.force_unwound). Re-render from the payload's status and current values rather than inferring state from which event fired.

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. Events are a latency optimisation over polling, not a durable log — there is no replay of missed events, so a reconnect should always be followed by a REST reconciliation.

Build against POST /sample-events first. Wire up every branch of your handler against the mock events before you place a single real position — see Testing your integration above.

Last updated