WebSocket Subscriptions

Connect to BlockVectra WebSocket endpoints for eth_subscribe newHeads and logs. Learn connection methods, filter rules, reconnection backoff, and recovery.

BlockVectra provides secure WebSocket connections (wss://) for streaming real-time Ethereum event subscriptions alongside standard JSON-RPC requests.

Available chains

WebSocket support is served dynamically per chain. You can check whether WebSocket subscriptions are active on a network by reading ws (boolean) and subscriptions (array of supported types) in the public GET /v1/chains endpoint.

The table below reflects networks where WebSocket support is enabled:

Chain{chain}WebSocket Endpoint (Path Key)Supported Subscriptions
Robinhood Chainrobinhood_mainnetwss://api.blockvectra.com/v1/robinhood_mainnet/{api_key}newHeads, logs

Connection and authentication

Clients establish a secure TLS WebSocket connection (wss://). The API key can be supplied in two ways:

  • Path key: wss://api.blockvectra.com/v1/{chain}/{api_key}
  • Header key: wss://api.blockvectra.com/v1/{chain} with the x-api-key: {api_key} or Authorization: Bearer {api_key} header during the HTTP Upgrade handshake.

Handshake admission checks

During the HTTP Upgrade handshake, the server evaluates admission gates before establishing the WebSocket session:

  • Authentication: An absent API key returns HTTP 401 (missing_api_key); an unknown, disabled, or revoked API key returns HTTP 401 (invalid_api_key); if key table synchronization is temporarily unavailable, the response is HTTP 503 (auth_unavailable).
  • Account balance: An account with zero or negative prepaid balance returns HTTP 402 (balance_exhausted); if billing state cannot be confirmed, the response is HTTP 503 (billing_unavailable).
  • Connection limits: Exceeding the per-key limit (20 connections per server instance) or per-account limit (50 connections per server instance) returns HTTP 429 (ws_connection_limit).
  • Chain availability: Requesting an unknown or unserved chain returns HTTP 404 (unknown_chain).
  • Instance capacity: When a server instance is at capacity or queued notifications exceed threshold, the handshake returns HTTP 503 (overloaded) with a Retry-After header.

Once connected, clients can send standard JSON-RPC 2.0 requests (such as eth_blockNumber or eth_call) and subscription control methods formatted as UTF-8 text frames.

Subscription methods

The API implements the standard Ethereum pub/sub interface: eth_subscribe and eth_unsubscribe.

newHeads

Emits a new block header object whenever a new block is appended to the chain head.

  • Subscribe request:
    {"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newHeads"]}
  • Subscribe response: Returns an opaque hexadecimal subscription identifier:
    {"jsonrpc":"2.0","id":1,"result":"0x1"}
  • Push notification frame:
    {"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x1","result":{"number":"0x1b4","hash":"0x..."}}}

logs

Emits log events matching specified filter criteria.

  • Filter requirement: Every logs subscription filter must specify an address (a contract address or array of addresses) or a topic0 (the first topic position, non-null). A filter specifying neither (such as {} or {"topics":[null,"0x..."]}) is rejected with error code -32602 (logs_filter_required).

  • Filter limits: At most 100 addresses; at most 4 topic positions with at most 16 candidate hashes per position.

  • Instance capacity: If active log filters on the server instance reach capacity, the subscription returns error code -32022 (ws_filter_capacity).

  • Chain reorganizations: If a block is removed due to a chain reorg, log notifications for removed logs carry "removed": true.

  • Subscribe request:

    {"jsonrpc":"2.0","id":2,"method":"eth_subscribe","params":["logs",{"address":"0x1234567890123456789012345678901234567890"}]}

eth_unsubscribe

Terminates an active subscription using its subscription identifier.

  • Unsubscribe request:
    {"jsonrpc":"2.0","id":3,"method":"eth_unsubscribe","params":["0x1"]}
  • Unsubscribe response:
    {"jsonrpc":"2.0","id":3,"result":true}

Runnable examples

Connect using viem v2 via createPublicClient and the webSocket transport. Replace {chain} with the target chain identifier and {api_key} with your API key:

import { createPublicClient, webSocket } from 'viem';

const apiKey = process.env.BLOCKVECTRA_API_KEY || '{api_key}';
const url = `wss://api.blockvectra.com/v1/{chain}/${apiKey}`;

const client = createPublicClient({
  transport: webSocket(url),
});

// 1. Subscribe to new block headers (newHeads)
const unwatchBlocks = client.watchBlocks({
  onBlock: (block) => {
    console.log('New block header received:', block.number, block.hash);
  },
  onError: (error) => {
    console.error('watchBlocks error:', error);
  },
});

// 2. Subscribe to contract event logs (filter requires address or topic0)
const unwatchEvents = client.watchEvent({
  address: '0x1234567890123456789012345678901234567890',
  onLogs: (logs) => {
    console.log('Matching logs received:', logs);
  },
  onError: (error) => {
    console.error('watchEvent error:', error);
  },
});

Close codes and client actions

When the server terminates a WebSocket session, it sends a Close frame with a specific close code and short reason. The table below lists the close codes emitted by the server and recommended actions:

Close codeReason stringDescriptionRetryableClient action
1001idleInactive connection without subscriptions or messages for 3600 seconds (1 hour)YesReconnect as needed.
1003binary frames are not acceptedBinary WebSocket frame received; UTF-8 text frames onlyNoDo not reconnect automatically. Update client to send text frames.
1009message too largeInbound payload exceeded 1 MiB (1,048,576 bytes)NoDo not reconnect automatically. Split large requests or reduce payload size.
1012service restartServer instance restarting, or session reached maximum lifetime (24 hours ± 10% jitter)YesReconnect using randomized jitter backoff, re-establish subscriptions, and backfill missed data.
1013chain unavailableChain WebSocket listener unavailable, or upstream subscription source disconnectedYesReconnect using full-jitter exponential backoff, re-establish subscriptions, and backfill missed data.
1013overloadedServer instance queued notifications buffer (256 MiB) reached capacityYesReconnect using full-jitter exponential backoff, re-establish subscriptions, and backfill missed data.
4402insufficient balanceAccount balance exhaustedNoDo not reconnect automatically. Top up your balance, then reconnect.
4404invalid api keyAPI key is unknown, disabled, or revokedNoDo not reconnect automatically. Verify or rotate API key in the console before reconnecting.
4408slow consumerThe server closes a session whose push queue passes 512 KiB (524,288 bytes) and drops pending notifications. The 4408 close frame reaches the client only if it is still reading (it is sent after the pushes already buffered, with a 2 s limit); a client that stopped reading or reads far slower than the push rate will instead see the connection drop without a close frame (browsers report 1006)YesTreat unexpected disconnects (no close frame received, browser reports 1006) like 4408: reconnect with backoff, re-establish subscriptions, and backfill dropped data with eth_getLogs; subscribe to less, or read faster.
4429push rate exceededNotification rate exceeded 1,000 pushes/second (burst 10,000)YesReduce subscriptions or narrow filters; reconnect with backoff, re-subscribe, and backfill.
4503billing unavailableBilling state or key table synchronization temporarily stale (> 60 seconds)YesTransient state; reconnect using full-jitter exponential backoff.

Reconnection and exponential backoff

To prevent synchronized reconnection storms when instances restart or connections drop, clients must implement exponential backoff with full jitter as defined in API Specification §15.4:

  • Backoff formula: Before the n-th reconnection attempt (n = 0, 1, 2, ...), wait for a duration chosen uniformly at random:
    delay = random(0, min(20s, 0.5s * 2^n))
  • Parameters (API Specification §15.4):
    • Base initial backoff: 0.5 s (0.5 s * 2^0 = 0.5 s)
    • Exponential multiplier: 2^n (0.5 s, 1.0 s, 2.0 s, 4.0 s, ...)
    • Maximum backoff cap: 20 s
    • Full jitter: Uniform pseudo-random value between 0 and min(20 s, 0.5 s * 2^n)
    • Reset counter: Reset retry counter n to 0 only after maintaining an uninterrupted, stable connection for at least 60 seconds
    • Close code 1012: Introduce randomized initial delay before first reconnect attempt to avoid synchronized reconnection spikes
    • Non-retryable codes: Do not reconnect automatically on 4402, 4404, 1003, or 1009.
    • Unexpected disconnects: Treat unexpected disconnects (no close frame received, browser reports 1006) like 4408: reconnect with backoff, and reduce subscriptions or read faster.

Backfilling missed data after reconnection

WebSocket subscriptions do not persist across connections; notifications emitted during a disconnection are not retained on the server. Following reconnection, clients should execute a catch-up strategy as outlined in API Specification §15.5:

  1. Backfill logs with eth_getLogs:
    • Persist the highest block number successfully processed (last_processed_block).
    • Immediately call eth_subscribe("logs", ...) on reconnect to capture live events.
    • Query missed blocks via eth_getLogs with fromBlock: last_processed_block + 1 and toBlock: "latest" (or the first block received from the live stream).
    • If the disconnection gap exceeds 1,000 blocks (max_logs_block_range: 1000), partition queries into chunks of at most 1,000 blocks each.
    • Deduplicate log entries across the query boundary using the unique tuple (blockHash, transactionHash, logIndex).
  2. Backfill block headers with eth_getBlockByNumber:
    • Record the latest block number and hash received before disconnect.
    • Re-subscribe to newHeads.
    • Query eth_getBlockByNumber("latest", false) and fetch missing intermediate blocks sequentially. Verify parentHash chain continuity to detect reorgs.

Limits

LimitValueResult when exceeded
Concurrent WebSocket connections per API key20 per server instanceUpgrade handshake returns 429 (ws_connection_limit)
Concurrent WebSocket connections per account50 per server instanceUpgrade handshake returns 429 (ws_connection_limit)
Subscriptions per WebSocket connection100-32022 subscription_limit
newHeads subscriptions per WebSocket connection4-32022 subscription_limit
logs subscription filter requirementsMust specify an address or a topic0 (first position in topics)-32602 logs_filter_required
Inbound WebSocket message size1 MiB (1048576 bytes)Connection closed with code 1009
Push notification queue buffer per connection512 KiB (524288 bytes)Connection closed with code 4408 (slow consumer)
Push notifications queued per server instance (all connections together)256 MiB (268435456 bytes)The connection whose next notification does not fit is closed with code 1013 (overloaded); past half of it, a new WebSocket handshake gets 503 (overloaded, with Retry-After) and eth_subscribe gets -32026 ws_push_overloaded
Answers not yet read per WebSocket connection16 MiBThe server reads no further message from the connection until the client has read enough of them
WebSocket client not reading30 s blocked on one writeConnection dropped without a close frame
Push notification rate per connection1000 pushes/second (burst 10000)Connection closed with code 4429
Idle WebSocket connection3600 s (1 hour) with no subscriptions and no messagesConnection closed with code 1001
WebSocket connection maximum age24 hours (with ±10% jitter)Connection closed with code 1012

Next steps

Last updated:

On this page