Skip to content

Realtime WebSocket protocol v2

The API gateway exposes one public realtime endpoint: /ws. The former /stream, thin account sockets, order-book listener, and candle socket are not part of protocol v2. All chain-derived account data remains publicly readable; subscriptions do not add authentication.

Locally, connect to ws://localhost:8090/ws. Production uses wss://api-v1-ab.blink.trade/ws.

Handshake and commands

The gateway's first frame is:

json
{"type":"hello","protocol_version":2,"heartbeat_interval_ms":25000}

Clients must reject any protocol version other than 2. Every client command has a non-empty request_id:

json
{
  "method":"subscribe",
  "request_id":"request_1",
  "subscription":{
    "channel":"account_state",
    "address":"0x0000000000000000000000000000000000000000",
    "subaccount_id":0
  }
}
json
{"method":"unsubscribe","request_id":"request_2","subscription_id":"sub_42"}
json
{"method":"ping","request_id":"request_3","timestamp":1784710800000}

The subscribe acknowledgement assigns the logical subscription's server ID:

json
{
  "type":"subscribed",
  "request_id":"request_1",
  "subscription_id":"sub_42",
  "channel":"account_state"
}

Snapshot, update, error, resync, and unsubscribe frames carry this subscription_id. A physical connection may hold any number of logical subscriptions.

Channels

ChannelRequired scopeSnapshot contents
account_stateaddress, subaccount_idMargin account, per-market leverage, positions, active spot and perp orders, spot balances, recent spot and perp fills, position fills, and funding payments.
account_catalogaddressSubaccounts and delegations.
marketsnoneMarket metadata, rolling values, margin tiers, and recent funding rates.
orderbookmarket_id, is_perpsComplete aggregated L2 book and sequence.
tradesmarket_id, is_perpsBounded recent trades.
fundingmarket_idRecent funding rates for a perp market.
mark_pricemarket_idCurrent perp mark and oracle prices.
market_statsoptional market_id, is_perpsCurrent rolling market statistics.
all_midsnoneCurrent perp mark price keyed by market ID.
bbomarket_id, is_perpsCurrent best bid and offer.
candlesmarket_id, is_perps, intervalCurrent trade candle.
mark_price_candlesmarket_id, is_perps: true, intervalCurrent perp mark-price candle.

Supported candle intervals are 1m, 5m, 15m, 1h, 4h, and 1d. The optional legacy snapshot request field is accepted, but protocol v2 is always snapshot-first.

Snapshot lifecycle

The gateway registers a subscription with live fanout before reading its snapshot. It then sends an atomic lifecycle:

json
{"type":"snapshot_begin","subscription_id":"sub_42","channel":"account_state"}
json
{
  "type":"snapshot_data",
  "subscription_id":"sub_42",
  "channel":"account_state",
  "operations":[
    {"collection":"positions","op":"reset","rows":[]},
    {"collection":"orders","op":"reset","rows":[]}
  ]
}
json
{"type":"snapshot_end","subscription_id":"sub_42","channel":"account_state"}

Large reset operations are split across snapshot_data frames. Consumers must buffer them and expose the replacement only at snapshot_end. Data-oriented channels use the same lifecycle with a data field instead of operations.

Account, catalog, trade, and market collection updates use insert, upsert, delete, or reset operations. Every operation produced by one sequencer transaction is carried in the same update frame:

json
{
  "type":"update",
  "subscription_id":"sub_42",
  "channel":"account_state",
  "operations":[
    {"collection":"orders","op":"delete","row":{"client_order_id":17}},
    {"collection":"fills","op":"insert","row":{"event_id":"991-3"}}
  ]
}

Append-only rows use stable {tx_index}-{ordinal} event IDs. The transaction watermark used to bridge a durable snapshot to the in-memory replay ring is internal and is not a client resume cursor. If the ring cannot bridge a read, the gateway retries the snapshot and then emits resync_required rather than installing partial state.

Order-book continuity

An order-book snapshot includes the complete book and its installed sequence. Every delta contains absolute quantities plus both continuity values:

json
{
  "type":"update",
  "subscription_id":"sub_7",
  "channel":"orderbook",
  "data":{
    "market_id":1,
    "is_perps":true,
    "bids":[["6500000","12"]],
    "asks":[],
    "previous_sequence":"91",
    "sequence":"92",
    "timestamp_ms":1784710800000
  }
}

A quantity of "0" removes the level. If previous_sequence differs from the installed sequence, discard the whole book and resubscribe. After an upstream disconnect, the gateway reseeds durable state and forces a fresh snapshot before subsequent deltas.

Liveness and recovery

Clients should ping every 25 seconds. The gateway replies with the same request_id and optional timestamp in a pong. It disconnects clients that are silent for 60 seconds. Per-connection and per-subscription queues are bounded; a slow consumer receives an error or resync_required and must reconnect or resubscribe for a new snapshot.

Errors contain stable code, human-readable error, and the applicable request_id or subscription_id. A subscription-local replay failure does not corrupt the other subscriptions on the same connection.