Skip to main content

WebSocket Feeds

The PolySimulator API provides two WebSocket feeds for real-time data:
FeedEndpointDescription
Price FeedWS /v1/ws/pricesReal-time price updates for subscribed markets
Execution FeedWS /v1/ws/executionsLimit order fill notifications

Authentication

WebSocket connections require a short-lived JWT token (60 seconds), separate from your API key.
1

Mint a WebSocket Token

TOKEN=$(curl -s -X POST \
  -H "X-API-Key: $API_KEY" \
  https://api.polysimulator.com/v1/keys/ws-token \
  | jq -r '.token')
Response:
{"token": "eyJhbGciOiJIUzI1NiIs...", "expires_in": 60}
2

Connect Immediately

wscat -c "wss://api.polysimulator.com/v1/ws/prices?token=$TOKEN"
The token expires in 60 seconds. Connect immediately after minting.

Protocol

All messages are JSON. The protocol supports these client actions:
ActionDescription
subscribeSubscribe to price updates for markets
unsubscribeStop receiving updates for markets
pingHeartbeat check

Ping/Pong

// Client sends:
{"action": "ping"}

// Server responds:
{"type": "pong", "ts": 1705312200000}

Connection Limits

TierMax WS ConnectionsMax Subscriptions/Connection
free250 markets
pro1050 markets
enterprise5050 markets

Reconnection & Token Rotation

WebSocket tokens expire after 60 seconds and are single-use. Your bot must implement automatic reconnection with fresh token minting.
import asyncio
import json
import websockets
import httpx

API_KEY = "your-api-key"
BASE_URL = "https://api.polysimulator.com"

async def mint_ws_token() -> str:
    """Mint a fresh 60-second WS token."""
    async with httpx.AsyncClient() as client:
        resp = await client.post(
            f"{BASE_URL}/v1/keys/ws-token",
            headers={"X-API-Key": API_KEY},
        )
        return resp.json()["token"]

async def connect_with_reconnect(subscriptions: list[str]):
    backoff = 1
    max_backoff = 30

    while True:
        try:
            token = await mint_ws_token()
            async with websockets.connect(
                f"wss://api.polysimulator.com/v1/ws/prices?token={token}"
            ) as ws:
                backoff = 1  # Reset on successful connect

                # Re-subscribe after reconnect
                await ws.send(json.dumps({
                    "action": "subscribe",
                    "markets": subscriptions,
                }))

                async for message in ws:
                    data = json.loads(message)
                    if data.get("type") == "pong":
                        continue
                    # Process price update
                    handle_price(data)

        except websockets.ConnectionClosedError as e:
            if e.code == 4001:
                # Token expired — mint fresh and reconnect immediately
                continue
            if e.code == 4002:
                # Connection limit — wait before retry
                await asyncio.sleep(backoff)
                backoff = min(backoff * 2, max_backoff)
        except Exception:
            await asyncio.sleep(backoff)
            backoff = min(backoff * 2, max_backoff)

Key Rules

  1. Never reuse tokens — mint a fresh token before each connect() call
  2. Re-subscribe after reconnect — the server does not remember your subscriptions
  3. Exponential backoff — start at 1s, cap at 30s, reset on successful connect
  4. Handle close code 4001 immediately — no backoff needed, just mint and reconnect

Best Practices

Connect Immediately

WS tokens expire in 60 seconds. Mint and connect in the same code block.

Implement Reconnection

Use exponential backoff for reconnection. Mint a fresh token on each reconnect attempt.

Prefer WS Over Polling

WebSocket subscriptions don’t count against REST rate limits. Use them instead of polling GET /v1/markets.

Use Execution Feed

Subscribe to the execution feed for limit order fill confirmations instead of polling GET /v1/orders.

Error Handling

WS Close CodeMeaning
4001Invalid or expired JWT token
4002Maximum WebSocket connections exceeded
When you receive close code 4001, mint a fresh token and reconnect. When you receive close code 4002, close idle connections before reconnecting.

Next Steps