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/wsentry point shipsPositionSocketandMarketSocketclients 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) andreact/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.
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:
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
authpayload (recommended):auth: { apiKey: "dm_live_skey_..." }(ordm_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_...(ordm_sbx_skey_...in sandbox). For Socket.IO clients, set this via theextraHeadersoption.
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
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
authpayload (recommended):auth: { token: "<jwt>" }.HTTP header:
Authorization: Bearer <jwt>. For Socket.IO clients, set this via theextraHeadersoption.
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:
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
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:
data.code
string
Machine-readable notification code
data.message
string
Human-readable description
data.params
object
Additional context (varies by notification code)
Notification codes:
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.
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.
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
datais a single object — market events always deliverdataas 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 iteratedataas 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 onenotificationevent 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_changedandmarket.max_leverage_changedare 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

