Skip to main content

WebSocket API

The WebSocket API provides real-time streaming of market data, trades, order book updates, and position changes.

Endpoint: ws://{host}:8084/ws

Connecting

Authenticate using your API key via the X-Api-Key header or the apiKey query parameter.

Via Header

const ws = new WebSocket('wss://polymarket.sandbox.playbatman.com/ws', {
headers: { 'X-Api-Key': 'your-api-key' }
});

Via Query Parameter

const ws = new WebSocket('wss://polymarket.sandbox.playbatman.com/ws?apiKey=your-api-key');

Authentication Response

On successful connection, you receive:

{
"type": "AUTHENTICATED",
"payload": {
"clientId": "ws-client-uuid",
"operatorId": "operator-uuid"
}
}

If authentication fails, the connection is closed with code 4001.

Subscribing to Markets

After connecting, subscribe to markets to receive updates.

Subscribe

{
"type": "SUBSCRIBE",
"payload": {
"marketIds": ["market-uuid-1", "market-uuid-2"]
}
}

You can also subscribe to a single market:

{
"type": "SUBSCRIBE",
"payload": {
"marketIds": "market-uuid-1"
}
}

Response:

{
"type": "SUBSCRIBED",
"payload": {
"marketIds": ["market-uuid-1", "market-uuid-2"]
}
}

Unsubscribe

{
"type": "UNSUBSCRIBE",
"payload": {
"marketIds": ["market-uuid-1"]
}
}

Response:

{
"type": "UNSUBSCRIBED",
"payload": {
"marketIds": ["market-uuid-1"]
}
}

Keep-Alive

Send periodic pings to keep the connection alive:

{ "type": "PING" }

Response:

{ "type": "PONG" }

Event Types

Once subscribed to a market, you receive the following event types:

PRICE_UPDATE

Fired when market prices change (after trades or order book changes).

{
"type": "PRICE_UPDATE",
"payload": {
"marketId": "market-uuid",
"prices": [
{
"marketId": "market-uuid",
"outcomeId": "outcome-uuid-1",
"outcomeName": "Yes",
"bestBid": 0.65,
"bestAsk": 0.68,
"lastPrice": 0.66,
"midPrice": 0.665,
"spread": 0.03,
"volume24h": 5000
},
{
"marketId": "market-uuid",
"outcomeId": "outcome-uuid-2",
"outcomeName": "No",
"bestBid": 0.32,
"bestAsk": 0.35,
"lastPrice": 0.34,
"midPrice": 0.335,
"spread": 0.03,
"volume24h": 4800
}
],
"timestamp": 1708293600000
}
}

ORDERBOOK_UPDATE

Fired when the order book for an outcome changes.

{
"type": "ORDERBOOK_UPDATE",
"payload": {
"marketId": "market-uuid",
"outcomeId": "outcome-uuid",
"orderBook": {
"marketId": "market-uuid",
"outcomeId": "outcome-uuid",
"bids": [
{ "price": 0.65, "shares": 150, "orderCount": 3 }
],
"asks": [
{ "price": 0.68, "shares": 75, "orderCount": 2 }
],
"bestBid": 0.65,
"bestAsk": 0.68,
"spread": 0.03,
"timestamp": 1708293600000
},
"timestamp": 1708293600000
}
}

TRADE

Fired when a trade executes in a subscribed market.

{
"type": "TRADE",
"payload": {
"marketId": "market-uuid",
"outcomeId": "outcome-uuid",
"price": 0.66,
"shares": 50,
"side": "BUY",
"timestamp": 1708293600000
}
}

MARKET_STATUS

Fired when a market's status changes (opened, suspended, closed, resolved, etc.).

{
"type": "MARKET_STATUS",
"payload": {
"marketId": "market-uuid",
"status": "RESOLVED",
"timestamp": 1708293600000
}
}

POSITION_UPDATE

Fired when a player's position in a market changes. Only sent to the operator that owns the position.

{
"type": "POSITION_UPDATE",
"payload": {
"marketId": "market-uuid",
"operatorId": "operator-uuid",
"playerId": "player-123",
"position": {
"id": "position-uuid",
"marketId": "market-uuid",
"outcomeId": "outcome-uuid",
"operatorId": "operator-uuid",
"playerId": "player-123",
"shares": "150",
"avgPrice": "0.62",
"totalCost": "93",
"realizedPnl": "0"
},
"timestamp": 1708293600000
}
}

ORDER_UPDATE

Fired when one of your operator's orders is updated (filled, partially filled, cancelled). Only sent to the operator that owns the order.

{
"type": "ORDER_UPDATE",
"payload": {
"marketId": "market-uuid",
"operatorId": "operator-uuid",
"order": {
"id": "order-uuid",
"marketId": "market-uuid",
"outcomeId": "outcome-uuid",
"operatorId": "operator-uuid",
"playerId": "player-123",
"side": "BUY",
"type": "LIMIT",
"shares": "50",
"price": "0.65",
"filledShares": "50",
"avgFillPrice": "0.63",
"remainingShares": "0",
"status": "FILLED",
"transactionId": "txn-001"
},
"timestamp": 1708293600000
}
}

ERROR

Sent when an error occurs processing a message.

{
"type": "ERROR",
"payload": {
"code": "INVALID_MESSAGE",
"message": "Unknown message type: INVALID"
}
}

Complete Example

const WebSocket = require('ws');

const ws = new WebSocket('wss://polymarket.sandbox.playbatman.com/ws?apiKey=your-api-key');

ws.on('open', () => {
console.log('Connected');
});

ws.on('message', (data) => {
const message = JSON.parse(data);

switch (message.type) {
case 'AUTHENTICATED':
console.log('Authenticated as', message.payload.operatorId);
// Subscribe to a market
ws.send(JSON.stringify({
type: 'SUBSCRIBE',
payload: { marketIds: ['market-uuid'] }
}));
break;

case 'SUBSCRIBED':
console.log('Subscribed to', message.payload.marketIds);
break;

case 'PRICE_UPDATE':
console.log('Price update:', message.payload.prices);
break;

case 'TRADE':
console.log('Trade:', message.payload);
break;

case 'ORDER_UPDATE':
console.log('Order update:', message.payload.order);
break;

case 'POSITION_UPDATE':
console.log('Position update:', message.payload.position);
break;

case 'MARKET_STATUS':
console.log('Market status:', message.payload.status);
break;

case 'PONG':
break;
}
});

// Keep-alive ping every 30 seconds
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'PING' }));
}
}, 30000);

Event Visibility

EventVisibility
PRICE_UPDATEAll subscribers of the market
ORDERBOOK_UPDATEAll subscribers of the market
TRADEAll subscribers of the market
MARKET_STATUSAll subscribers of the market
POSITION_UPDATEOnly the operator that owns the position
ORDER_UPDATEOnly the operator that owns the order

Alternatives for Backend Integration

WebSocket is ideal for browser-based real-time UIs. For backend-to-backend integration, consider these alternatives:

  • Webhooks — Receive HTTP POST notifications when events occur. More reliable than maintaining a persistent WebSocket from your server. Includes HMAC-SHA256 signing and automatic retries.
  • Event Replay — Fetch missed events via REST. Events are retained for 24 hours with monotonically increasing sequence numbers for reliable pagination.

A recommended pattern for backend integration is to use webhooks for real-time delivery and event replay as a safety net to catch any events missed during webhook downtime.