Skip to content
🌐Network: Mainnet

WebSocket

Real-time data streaming via WebSocket connection.

Try It Live

Test the WebSocket API directly in your browser. Connect, send subscriptions, and see real-time responses.

Disconnected
wss://api.o2.app/v1/ws

Quick Actions

Subscribe
Unsubscribe

Send Message

Message Log

No messages yet. Connect and send a subscription to see responses.

Connection

URL: wss://api.testnet.o2.app/v1/ws

javascript
const ws = new WebSocket('wss://api.testnet.o2.app/v1/ws');

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

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

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

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

Connection Limits

ParameterValueDescription
Inactivity timeout60 secondsConnection closes if no messages received
Max subscription topics10Maximum identities per subscription request
Write buffer size4 KiBServer-side write buffer

Heartbeat (Keep-Alive)

Send PING as plain text to keep the connection alive. Server responds with PONG.

javascript
// Send PING every 30 seconds to prevent timeout
setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send('PING');
  }
}, 30000);

ws.onmessage = (event) => {
  if (event.data === 'PONG') {
    console.log('Heartbeat OK');
    return;
  }
  // Handle other messages...
};

Subscription Actions

All subscription messages use JSON format with an action field.

subscribe_depth

Subscribe to full order book depth updates with real-time changes.

Request:

json
{
  "action": "subscribe_depth",
  "market_id": "0xc0c5c48315d8cfff9f00777f81fdf04e9b4ef69e802a1a068549a8d6a06ee19f",
  "precision": "10"
}
FieldTypeRequiredDescription
actionstringYes"subscribe_depth"
market_idstringYesMarket ID (hex, 0x-prefixed). See Get Markets List for available markets
precisionstringYesPrice aggregation level. Valid: "10", "100", "1000", "10000", "100000", "1000000", "10000000", "100000000", "1000000000"

Initial Response (full state):

The first message after subscribing contains the complete current state of the order book in the view field.

json
{
  "action": "subscribe_depth",
  "market_id": "0xc0c5c48315d8cfff9f00777f81fdf04e9b4ef69e802a1a068549a8d6a06ee19f",
  "view": {
    "precision": 10,
    "buys": [
      { "price": "95000000000", "quantity": "1500000000" }
    ],
    "sells": [
      { "price": "95100000000", "quantity": "2000000000" }
    ]
  }
}

Update Response (delta changes):

All subsequent messages contain only the changes since the last update in the changes field this is not a full state. Apply these deltas to your local order book. A quantity of "0" means the price level has been removed.

json
{
  "action": "subscribe_depth",
  "market_id": "0xc0c5c48315d8cfff9f00777f81fdf04e9b4ef69e802a1a068549a8d6a06ee19f",
  "changes": {
    "buys": [
      { "price": "95000000000", "quantity": "1600000000" }
    ],
    "sells": []
  },
  "onchain_timestamp": "1704067200",
  "seen_timestamp": "1704067200123456789"
}

subscribe_depth_view

Subscribe to a smoothed view of the order book at a specified frequency. Useful for visualizing the state of the order book in a rendered chart, but not recommended for trading since it does not provide a full picture of the market state.

Request:

json
{
  "action": "subscribe_depth_view",
  "market_id": "0xc0c5c48315d8cfff9f00777f81fdf04e9b4ef69e802a1a068549a8d6a06ee19f",
  "precision": "10",
  "frequency": "500ms"
}
FieldTypeRequiredDescription
actionstringYes"subscribe_depth_view"
market_idstringYesMarket ID (hex, 0x-prefixed)
precisionstringYesPrice aggregation level
frequencystringYesUpdate frequency: "100ms", "500ms", "1s", "3s"

Response:

json
{
  "action": "subscribe_depth_view",
  "market_id": "0xc0c5c48315d8cfff9f00777f81fdf04e9b4ef69e802a1a068549a8d6a06ee19f",
  "view": {
    "precision": 10,
    "buys": [
      { "price": "95000000000", "quantity": "1500000000" },
      { "price": "94900000000", "quantity": "3200000000" }
    ],
    "sells": [
      { "price": "95100000000", "quantity": "2000000000" },
      { "price": "95200000000", "quantity": "1800000000" }
    ]
  }
}

subscribe_trades

Subscribe to trade executions for a market.

Request:

json
{
  "action": "subscribe_trades",
  "market_id": "0xc0c5c48315d8cfff9f00777f81fdf04e9b4ef69e802a1a068549a8d6a06ee19f"
}
FieldTypeRequiredDescription
actionstringYes"subscribe_trades"
market_idstringYesMarket ID (hex, 0x-prefixed)

Response:

json
{
  "action": "subscribe_trades",
  "market_id": "0xc0c5c48315d8cfff9f00777f81fdf04e9b4ef69e802a1a068549a8d6a06ee19f",
  "trades": [
    {
      "trade_id": "123456789",
      "side": "Buy",
      "quantity": "100000000",
      "price": "95050000000",
      "total": "9505000000000000000",
      "timestamp": "1704067200123456789"
    }
  ],
  "onchain_timestamp": "1704067200",
  "seen_timestamp": "1704067200123456789"
}
Response FieldTypeDescription
actionstring"subscribe_trades"
market_idstringMarket ID
tradesarrayArray of trade objects
trades[].trade_idstringUnique trade identifier
trades[].sidestring"Buy" or "Sell" (taker side)
trades[].quantitystringTrade quantity (base asset, raw units)
trades[].pricestringExecution price (raw units)
trades[].totalstringTotal value (quantity * price)
trades[].timestampstringTrade timestamp (nanoseconds since Unix epoch)
onchain_timestampstringWhen the event occurred on-chain (seconds since Unix epoch)
seen_timestampstringWhen the indexer processed the event (nanoseconds since Unix epoch)

subscribe_orders

Subscribe to order updates for specific trading accounts.

Request:

json
{
  "action": "subscribe_orders",
  "identities": [
    { "ContractId": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" }
  ]
}
FieldTypeRequiredDescription
actionstringYes"subscribe_orders"
identitiesIdentity[]YesArray of Identity objects (max 10)

Response:

json
{
  "action": "subscribe_orders",
  "orders": [
    {
      "order_id": "0xabc123...",
      "owner": { "ContractId": "0x1234..." },
      "side": "Buy",
      "order_type": "Limit",
      "market_id": "0xc0c5c48...",
      "quantity": "100000000",
      "quantity_fill": "50000000",
      "price": "95000000000",
      "price_fill": "95000000000",
      "timestamp": "1704067200123456789",
      "close": false,
      "partially_filled": true,
      "cancel": false,
      "history": [
        {
          "type": "created",
          "status": "success",
          "tx_id": "0xdef456..."
        }
      ],
      "fills": [
        {
          "order_id": "0xfff...",
          "quantity": "50000000",
          "price": "95000000000",
          "timestamp": "1704067200123456789",
          "fee": "100000"
        }
      ]
    }
  ],
  "onchain_timestamp": "1704067200",
  "seen_timestamp": "1704067200123456789"
}
Response FieldTypeDescription
order_idstringUnique order identifier
ownerIdentityOwner of the order
sidestring"Buy" or "Sell"
order_typestring"Limit" or "Market"
market_idstringMarket ID
quantitystringTotal order quantity
quantity_fillstringFilled quantity
pricestringOrder price
price_fillstringAverage fill price
timestampstringOrder creation timestamp
closebooleanOrder is fully closed
partially_filledbooleanOrder has partial fills
cancelbooleanOrder was cancelled
historyarrayTransaction history
fillsarrayIndividual fill events

subscribe_balances

Subscribe to balance updates for specific trading accounts.

Request:

json
{
  "action": "subscribe_balances",
  "identities": [
    { "ContractId": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" }
  ]
}
FieldTypeRequiredDescription
actionstringYes"subscribe_balances"
identitiesIdentity[]YesArray of Identity objects (max 10)

Response:

json
{
  "action": "subscribe_balances",
  "balance": [
    {
      "identity": { "ContractId": "0x1234..." },
      "asset_id": "0xabcd...",
      "total_locked": "500000000",
      "total_unlocked": "1500000000",
      "trading_account_balance": "2000000000",
      "order_books": {
        "0xc0c5c48...": {
          "locked": "500000000",
          "unlocked": "0"
        }
      }
    }
  ],
  "onchain_timestamp": "1704067200",
  "seen_timestamp": "1704067200123456789"
}
Response FieldTypeDescription
identityIdentityTrading account identity
asset_idstringAsset ID
total_lockedstringTotal locked across all order books
total_unlockedstringTotal unlocked across all order books
trading_account_balancestringTotal balance in trading account
order_booksobjectPer-orderbook breakdown (market_id -> balances)

subscribe_nonce

Subscribe to contract nonce updates. Useful for tracking transaction ordering.

Request:

json
{
  "action": "subscribe_nonce",
  "identities": [
    { "ContractId": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" }
  ]
}
FieldTypeRequiredDescription
actionstringYes"subscribe_nonce"
identitiesIdentity[]YesArray of Identity objects (max 10)

Response:

json
{
  "action": "subscribe_nonce",
  "contract_id": "0x1234...",
  "nonce": "42",
  "onchain_timestamp": "1704067200",
  "seen_timestamp": "1704067200123456789"
}

Unsubscribe Actions

To stop receiving updates, send the corresponding unsubscribe action.

unsubscribe_depth

json
{
  "action": "unsubscribe_depth",
  "market_id": "0xc0c5c48315d8cfff9f00777f81fdf04e9b4ef69e802a1a068549a8d6a06ee19f"
}

unsubscribe_depth_view

json
{
  "action": "unsubscribe_depth_view",
  "market_id": "0xc0c5c48315d8cfff9f00777f81fdf04e9b4ef69e802a1a068549a8d6a06ee19f"
}

unsubscribe_trades

json
{
  "action": "unsubscribe_trades",
  "market_id": "0xc0c5c48315d8cfff9f00777f81fdf04e9b4ef69e802a1a068549a8d6a06ee19f"
}

unsubscribe_orders

json
{
  "action": "unsubscribe_orders"
}

unsubscribe_balances

json
{
  "action": "unsubscribe_balances",
  "identities": [
    { "ContractId": "0x1234..." }
  ]
}

unsubscribe_nonce

json
{
  "action": "unsubscribe_nonce",
  "identities": [
    { "ContractId": "0x1234..." }
  ]
}

Error Handling

Errors are returned as JSON with an error field.

Parse Error:

json
{
  "error": "expected value at line 1 column 1"
}

Validation Error:

json
{
  "error": "Cannot subscribe to more than 10 topics at once"
}

Invalid Frequency:

json
{
  "error": "Invalid subscription frequency: 200ms"
}

Common Errors

ErrorCauseSolution
Cannot subscribe to more than 10 topics at onceToo many identities in requestReduce identities array to 10 or fewer
Invalid subscription frequencyInvalid frequency valueUse 100ms, 500ms, 1s, or 3s
Client is inactiveNo messages for 60 secondsImplement heartbeat with PING
expected value at line X column YMalformed JSONCheck JSON syntax

Timestamps

All responses include two timestamp fields:

FieldFormatDescription
onchain_timestampSeconds since Unix epochWhen the event occurred on-chain (may be null)
seen_timestampNanoseconds since Unix epochWhen the indexer processed the event

Complete Example

javascript
const ws = new WebSocket('wss://api.testnet.o2.app/v1/ws');

// Connection opened
ws.onopen = () => {
  console.log('Connected to O2 WebSocket');

  // Subscribe to depth view
  ws.send(JSON.stringify({
    action: 'subscribe_depth_view',
    market_id: '0xc0c5c48315d8cfff9f00777f81fdf04e9b4ef69e802a1a068549a8d6a06ee19f',
    precision: '10',
    frequency: '500ms'
  }));

  // Subscribe to trades
  ws.send(JSON.stringify({
    action: 'subscribe_trades',
    market_id: '0xc0c5c48315d8cfff9f00777f81fdf04e9b4ef69e802a1a068549a8d6a06ee19f'
  }));

  // Start heartbeat
  setInterval(() => ws.send('PING'), 30000);
};

// Handle messages
ws.onmessage = (event) => {
  if (event.data === 'PONG') return;

  const data = JSON.parse(event.data);

  if (data.error) {
    console.error('Error:', data.error);
    return;
  }

  switch (data.action) {
    case 'subscribe_depth_view':
      console.log('Order book:', data.view);
      break;
    case 'subscribe_trades':
      console.log('Trades:', data.trades);
      break;
    default:
      console.log('Unknown action:', data.action);
  }
};

// Handle errors and reconnection
ws.onclose = (event) => {
  console.log('Disconnected, reconnecting in 5s...');
  setTimeout(() => {
    // Reconnect logic here
  }, 5000);
};

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

Reconnection Strategy

Implement exponential backoff for reconnection:

javascript
class O2WebSocket {
  constructor(url) {
    this.url = url;
    this.retryCount = 0;
    this.maxRetry = 10;
    this.connect();
  }

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

    this.ws.onopen = () => {
      this.retryCount = 0;
      // Resubscribe to topics here
    };

    this.ws.onclose = () => {
      if (this.retryCount < this.maxRetry) {
        const delay = Math.min(2000 + this.retryCount * 1500, 10000);
        this.retryCount++;
        setTimeout(() => this.connect(), delay);
      }
    };
  }
}

Backoff Schedule:

RetryDelay
12.0s
23.5s
35.0s
46.5s
58.0s
6+10.0s (max)