Skip to main content

WebSocket API

Real-time subscriptions for market updates, trades, and order book changes.

Connection

Endpoint

EnvironmentURL
Productionwss://api.seesaw.markets/ws
Testnetwss://api.testnet.seesaw.markets/ws

Connection Example

const ws = new WebSocket('wss://api.seesaw.markets/ws');

ws.onopen = () => {
  console.log('Connected');
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  console.log('Received:', message);
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

ws.onclose = () => {
  console.log('Disconnected');
};

Message Format

Client Messages

interface ClientMessage {
  type: 'subscribe' | 'unsubscribe' | 'ping';
  channel?: string;
  marketId?: string;
  // Additional parameters
}

Server Messages

interface ServerMessage {
  type: 'subscribed' | 'unsubscribed' | 'update' | 'error' | 'pong';
  channel?: string;
  data?: any;
  error?: string;
  timestamp: number;
}

Channels

market.update

Receive updates when market state changes.

Subscribe:

{
  "type": "subscribe",
  "channel": "market.update",
  "marketId": "1234567"
}

Updates:

{
  "type": "update",
  "channel": "market.update",
  "data": {
    "marketId": "1234567",
    "field": "startPrice",
    "value": "68543210000",
    "state": "TRADING",
    "timestamp": 1704067200
  },
  "timestamp": 1704067200
}

Fields that trigger updates:

  • startPrice - Start snapshot captured
  • endPrice - End snapshot captured
  • outcome - Market resolved
  • state - State transition

orderbook.update

Receive order book updates.

Subscribe:

{
  "type": "subscribe",
  "channel": "orderbook.update",
  "marketId": "1234567"
}

Updates:

{
  "type": "update",
  "channel": "orderbook.update",
  "data": {
    "marketId": "1234567",
    "action": "add",
    "side": "bid",
    "order": {
      "orderId": "123456789",
      "owner": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
      "priceBps": 5800,
      "quantity": "10000000000"
    },
    "bestBid": 5800,
    "bestAsk": 6000,
    "timestamp": 1704067300
  },
  "timestamp": 1704067300
}

Actions:

  • add - New order added
  • remove - Order cancelled or fully filled
  • update - Order partially filled

orderbook.snapshot

Receive full order book snapshots periodically.

Subscribe:

{
  "type": "subscribe",
  "channel": "orderbook.snapshot",
  "marketId": "1234567",
  "interval": 5000
}

Snapshots:

{
  "type": "update",
  "channel": "orderbook.snapshot",
  "data": {
    "marketId": "1234567",
    "bestBid": 5800,
    "bestAsk": 6000,
    "bids": [
      { "price": 5800, "quantity": "10000000000" },
      { "price": 5700, "quantity": "25000000000" }
    ],
    "asks": [
      { "price": 6000, "quantity": "5000000000" },
      { "price": 6100, "quantity": "12000000000" }
    ],
    "timestamp": 1704067300
  },
  "timestamp": 1704067300
}

trade.new

Receive new trade notifications.

Subscribe:

{
  "type": "subscribe",
  "channel": "trade.new",
  "marketId": "1234567"
}

Updates:

{
  "type": "update",
  "channel": "trade.new",
  "data": {
    "id": "trade_abc123",
    "marketId": "1234567",
    "taker": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
    "maker": "8yLYtg3CW87d97TXJSDpbD5jBkheTqA83TZRuJosgBsV",
    "side": "BuyYes",
    "priceBps": 5800,
    "quantity": "5000000000",
    "timestamp": 1704067500,
    "signature": "5wHu..."
  },
  "timestamp": 1704067500
}

position.update

Receive updates for a specific position.

Subscribe:

{
  "type": "subscribe",
  "channel": "position.update",
  "marketId": "1234567",
  "owner": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
}

Updates:

{
  "type": "update",
  "channel": "position.update",
  "data": {
    "marketId": "1234567",
    "owner": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
    "yesShares": "105000000000",
    "noShares": "50000000000",
    "change": {
      "field": "yesShares",
      "delta": "5000000000",
      "reason": "trade"
    },
    "timestamp": 1704067500
  },
  "timestamp": 1704067500
}

oracle.price

Receive Pyth oracle price updates.

Subscribe:

{
  "type": "subscribe",
  "channel": "oracle.price",
  "pythFeed": "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"
}

Updates:

{
  "type": "update",
  "channel": "oracle.price",
  "data": {
    "feed": "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43",
    "price": "68550000000",
    "confidence": "12000000",
    "exponent": -8,
    "publishTime": 1704067501
  },
  "timestamp": 1704067501
}

Subscription Management

Subscribe

{
  "type": "subscribe",
  "channel": "market.update",
  "marketId": "1234567"
}

Response:

{
  "type": "subscribed",
  "channel": "market.update",
  "marketId": "1234567",
  "timestamp": 1704067200
}

Unsubscribe

{
  "type": "unsubscribe",
  "channel": "market.update",
  "marketId": "1234567"
}

Response:

{
  "type": "unsubscribed",
  "channel": "market.update",
  "marketId": "1234567",
  "timestamp": 1704067200
}

Ping/Pong

Keep connection alive:

{ "type": "ping" }

Response:

{
  "type": "pong",
  "timestamp": 1704067200
}

Error Handling

Error Response

{
  "type": "error",
  "error": "INVALID_CHANNEL",
  "message": "Unknown channel: invalid.channel",
  "timestamp": 1704067200
}

Error Codes

CodeDescription
INVALID_CHANNELUnknown channel name
INVALID_MARKETInvalid market ID
SUBSCRIPTION_LIMITToo many subscriptions
RATE_LIMITEDToo many messages
INTERNAL_ERRORServer error

Reconnection

Strategy

class SeesawWebSocket {
  constructor(url) {
    this.url = url;
    this.subscriptions = new Map();
    this.connect();
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log('Connected');
      this.resubscribe();
    };

    this.ws.onclose = () => {
      console.log('Disconnected, reconnecting...');
      setTimeout(() => this.connect(), 1000);
    };

    this.ws.onmessage = (event) => {
      this.handleMessage(JSON.parse(event.data));
    };
  }

  subscribe(channel, params, callback) {
    const key = `${channel}:${JSON.stringify(params)}`;
    this.subscriptions.set(key, { channel, params, callback });

    this.ws.send(
      JSON.stringify({
        type: 'subscribe',
        channel,
        ...params,
      })
    );
  }

  resubscribe() {
    for (const [key, sub] of this.subscriptions) {
      this.ws.send(
        JSON.stringify({
          type: 'subscribe',
          channel: sub.channel,
          ...sub.params,
        })
      );
    }
  }

  handleMessage(message) {
    if (message.type === 'update') {
      const key = `${message.channel}:${JSON.stringify(message.data.marketId || {})}`;
      const sub = this.subscriptions.get(key);
      if (sub) {
        sub.callback(message.data);
      }
    }
  }
}

// Usage
const ws = new SeesawWebSocket('wss://api.seesaw.markets/ws');

ws.subscribe('market.update', { marketId: '1234567' }, (data) => {
  console.log('Market update:', data);
});

ws.subscribe('trade.new', { marketId: '1234567' }, (data) => {
  console.log('New trade:', data);
});

Complete Example

Real-Time Trading Dashboard

class TradingDashboard {
  constructor(marketId) {
    this.marketId = marketId;
    this.market = null;
    this.orderbook = { bids: [], asks: [] };
    this.trades = [];

    this.connect();
  }

  connect() {
    this.ws = new WebSocket('wss://api.seesaw.markets/ws');

    this.ws.onopen = () => {
      // Subscribe to all relevant channels
      this.ws.send(
        JSON.stringify({
          type: 'subscribe',
          channel: 'market.update',
          marketId: this.marketId,
        })
      );

      this.ws.send(
        JSON.stringify({
          type: 'subscribe',
          channel: 'orderbook.update',
          marketId: this.marketId,
        })
      );

      this.ws.send(
        JSON.stringify({
          type: 'subscribe',
          channel: 'trade.new',
          marketId: this.marketId,
        })
      );

      // Start ping interval
      this.pingInterval = setInterval(() => {
        this.ws.send(JSON.stringify({ type: 'ping' }));
      }, 30000);
    };

    this.ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      this.handleMessage(message);
    };

    this.ws.onclose = () => {
      clearInterval(this.pingInterval);
      setTimeout(() => this.connect(), 1000);
    };
  }

  handleMessage(message) {
    if (message.type !== 'update') return;

    switch (message.channel) {
      case 'market.update':
        this.handleMarketUpdate(message.data);
        break;
      case 'orderbook.update':
        this.handleOrderbookUpdate(message.data);
        break;
      case 'trade.new':
        this.handleNewTrade(message.data);
        break;
    }

    this.render();
  }

  handleMarketUpdate(data) {
    if (data.field === 'outcome') {
      console.log(`Market resolved: ${data.value === 1 ? 'UP' : 'DOWN'}`);
    }
    this.market = { ...this.market, [data.field]: data.value };
  }

  handleOrderbookUpdate(data) {
    if (data.action === 'add') {
      const side = data.side === 'bid' ? this.orderbook.bids : this.orderbook.asks;
      side.push(data.order);
      side.sort((a, b) =>
        data.side === 'bid' ? b.priceBps - a.priceBps : a.priceBps - b.priceBps
      );
    } else if (data.action === 'remove') {
      this.orderbook.bids = this.orderbook.bids.filter((o) => o.orderId !== data.order.orderId);
      this.orderbook.asks = this.orderbook.asks.filter((o) => o.orderId !== data.order.orderId);
    }
  }

  handleNewTrade(data) {
    this.trades.unshift(data);
    if (this.trades.length > 50) {
      this.trades.pop();
    }
  }

  render() {
    // Update UI
    console.log('=== Dashboard Update ===');
    console.log('Best Bid:', this.orderbook.bids[0]?.priceBps);
    console.log('Best Ask:', this.orderbook.asks[0]?.priceBps);
    console.log('Recent Trades:', this.trades.length);
  }
}

// Usage
const dashboard = new TradingDashboard('1234567');

Next Steps