Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

WebSocket

Endpoint: wss://v4-api.dev.parcllabs.com/v1/ws

The WebSocket provides real-time data: orderbook updates, fills, price changes, and block production. Connect, subscribe to channels, and receive events.

Connecting

const ws = new WebSocket("wss://v4-api.dev.parcllabs.com/v1/ws");

Send { "type": "ping" } to get a pong back with current block height and timing info. Use this to measure latency and confirm the connection is alive.

Subscribing

Send a subscribe message with the channels you want:

{
  "type": "subscribe",
  "channels": [
    { "channel": "orderbook", "market_id": 0 },
    { "channel": "trades", "market_id": 0 }
  ]
}

The server does not send a confirmation message โ€” subscriptions are silent. You will begin receiving events on the subscribed channels immediately.

To unsubscribe:

{
  "type": "unsubscribe",
  "channels": [{ "channel": "orderbook", "market_id": 0 }]
}

Channels

orderbook

Full orderbook snapshot on subscribe and on every subsequent change. Each message contains the complete book state โ€” there are no incremental deltas.

Subscribe: { "channel": "orderbook", "market_id": 0 }

{
  "type": "orderbookSnapshot",
  "market_id": 0,
  "bids": [
    { "price": "58100000000", "size": "500000", "orderCount": 1 },
    { "price": "58050000000", "size": "1200000", "orderCount": 3 }
  ],
  "asks": [
    { "price": "58200000000", "size": "300000", "orderCount": 1 },
    { "price": "58250000000", "size": "800000", "orderCount": 2 }
  ]
}

A new snapshot is sent on every order, fill, or cancellation that changes the book. Replace your local state entirely on each message.

trades

Fires on every trade in a market. Uses the same fill event format as the fills channel.

Subscribe: { "channel": "trades", "market_id": 0 }

{
  "type": "fill",
  "market_id": 0,
  "price": "58150000000",
  "size": "100000",
  "taker_order_id": 42,
  "maker_order_id": 38,
  "taker_account_id": 12,
  "maker_account_id": 3,
  "taker_side": "Long",
  "taker_fee": "0",
  "maker_fee": "0",
  "tx_hash": "9dd57899..."
}

fills

Your fills (requires your account ID). Fires when your orders get filled.

Subscribe: { "channel": "fills", "account_id": 12 }

{
  "type": "fill",
  "market_id": 0,
  "price": "58150000000",
  "size": "100000",
  "taker_order_id": 42,
  "maker_order_id": 38,
  "taker_account_id": 12,
  "maker_account_id": 3,
  "taker_side": "Long",
  "taker_fee": "203000",
  "maker_fee": "-58000",
  "tx_hash": "9dd57899..."
}

The event is sent to both the taker's and the maker's fills channel. Check whether your account_id matches taker_account_id or maker_account_id to determine your side and fee. Negative fees indicate a maker rebate.

orders

Your order updates (placed and canceled).

Subscribe: { "channel": "orders", "account_id": 12 }

Placed:

{
  "type": "orderPlaced",
  "order_id": 42,
  "account_id": 12,
  "market_id": 0,
  "side": "Long",
  "price": "58000000000",
  "size": "100000"
}

Canceled:

{
  "type": "orderCanceled",
  "order_id": 42,
  "account_id": 12,
  "market_id": 0,
  "reason": "UserCanceled"
}

liquidations

Fires when your account is liquidated.

Subscribe: { "channel": "liquidations", "account_id": 12 }

{
  "type": "liquidation",
  "account_id": 12,
  "positions_closed": 1,
  "liquidation_fee": "250000",
  "positions_remaining": 0
}

positions

Subscribe: { "channel": "positions", "account_id": 12 }

Position updates are delivered indirectly via fills and liquidations events rather than as a separate position snapshot. To get your current position state after a fill, call GET /v1/accounts/{id}/positions.

funding

Funding rate updates for a market. Fires hourly when funding settles.

Subscribe: { "channel": "funding", "market_id": 0 }

{
  "type": "fundingUpdate",
  "market_id": 0,
  "funding_rate": "-15800",
  "last_funding_timestamp": 1712345678000
}

oracleUpdates

Oracle price updates for a market.

Subscribe: { "channel": "oracleUpdates", "market_id": 0 }

{
  "type": "oraclePriceUpdate",
  "market_id": 0,
  "price": "58035000000",
  "previous_price": "57890000000",
  "positions_updated": 14,
  "valid_until": 1712433900
}

explorer

Block production info. Fires on every finalized block.

Subscribe: { "channel": "explorer" }

{
  "type": "blockFinalized",
  "height": 73001,
  "hash": "abc123...",
  "parent_hash": "def456...",
  "state_root": "4d419b60...",
  "proposer": "e1e7af3f...",
  "timestamp": 1712345678943,
  "tx_count": 3,
  "transactions": [
    {
      "hash": "0x9dd57899...",
      "tx_index": 0,
      "tx_type": "PlaceOrder",
      "sender": "0x35504e68...",
      "status": "success",
      "timestamp": 1712345678943
    }
  ]
}

Only the first transaction in each block is sent over WebSocket. For the full transaction list, use the block explorer REST API.

Ping/pong

Send a ping to measure latency and get current block info:

{ "type": "ping" }

Response:

{
  "type": "pong",
  "blockHeight": 73001,
  "blockTimeMs": 200
}

Example: orderbook tracker

const WebSocket = require("ws");
 
const ws = new WebSocket("wss://v4-api.dev.parcllabs.com/v1/ws");
 
let bids = [];
let asks = [];
 
ws.on("open", () => {
  ws.send(
    JSON.stringify({
      type: "subscribe",
      channels: [{ channel: "orderbook", market_id: 0 }],
    }),
  );
});
 
ws.on("message", (data) => {
  const msg = JSON.parse(data);
 
  if (msg.type === "orderbookSnapshot") {
    // Full snapshot on every change โ€” replace local state entirely
    bids = msg.bids; // [{ price, size, orderCount }, ...]
    asks = msg.asks;
 
    if (bids.length > 0 && asks.length > 0) {
      console.log(
        `Best bid: ${bids[0].price}, Best ask: ${asks[0].price}, Spread: ${asks[0].price - bids[0].price}`,
      );
    }
  }
});