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 Chain | robinhood_mainnet | wss://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 thex-api-key: {api_key}orAuthorization: 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 aRetry-Afterheader.
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
logssubscription filter must specify anaddress(a contract address or array of addresses) or atopic0(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 code | Reason string | Description | Retryable | Client action |
|---|---|---|---|---|
| 1001 | idle | Inactive connection without subscriptions or messages for 3600 seconds (1 hour) | Yes | Reconnect as needed. |
| 1003 | binary frames are not accepted | Binary WebSocket frame received; UTF-8 text frames only | No | Do not reconnect automatically. Update client to send text frames. |
| 1009 | message too large | Inbound payload exceeded 1 MiB (1,048,576 bytes) | No | Do not reconnect automatically. Split large requests or reduce payload size. |
| 1012 | service restart | Server instance restarting, or session reached maximum lifetime (24 hours ± 10% jitter) | Yes | Reconnect using randomized jitter backoff, re-establish subscriptions, and backfill missed data. |
| 1013 | chain unavailable | Chain WebSocket listener unavailable, or upstream subscription source disconnected | Yes | Reconnect using full-jitter exponential backoff, re-establish subscriptions, and backfill missed data. |
| 1013 | overloaded | Server instance queued notifications buffer (256 MiB) reached capacity | Yes | Reconnect using full-jitter exponential backoff, re-establish subscriptions, and backfill missed data. |
| 4402 | insufficient balance | Account balance exhausted | No | Do not reconnect automatically. Top up your balance, then reconnect. |
| 4404 | invalid api key | API key is unknown, disabled, or revoked | No | Do not reconnect automatically. Verify or rotate API key in the console before reconnecting. |
| 4408 | slow consumer | The 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) | Yes | Treat 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. |
| 4429 | push rate exceeded | Notification rate exceeded 1,000 pushes/second (burst 10,000) | Yes | Reduce subscriptions or narrow filters; reconnect with backoff, re-subscribe, and backfill. |
| 4503 | billing unavailable | Billing state or key table synchronization temporarily stale (> 60 seconds) | Yes | Transient 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
0andmin(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.
- Base initial backoff:
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:
- 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_getLogswithfromBlock: last_processed_block + 1andtoBlock: "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).
- Persist the highest block number successfully processed (
- 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. VerifyparentHashchain continuity to detect reorgs.
Limits
| Limit | Value | Result when exceeded |
|---|---|---|
| Concurrent WebSocket connections per API key | 20 per server instance | Upgrade handshake returns 429 (ws_connection_limit) |
| Concurrent WebSocket connections per account | 50 per server instance | Upgrade handshake returns 429 (ws_connection_limit) |
| Subscriptions per WebSocket connection | 100 | -32022 subscription_limit |
newHeads subscriptions per WebSocket connection | 4 | -32022 subscription_limit |
logs subscription filter requirements | Must specify an address or a topic0 (first position in topics) | -32602 logs_filter_required |
| Inbound WebSocket message size | 1 MiB (1048576 bytes) | Connection closed with code 1009 |
| Push notification queue buffer per connection | 512 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 connection | 16 MiB | The server reads no further message from the connection until the client has read enough of them |
| WebSocket client not reading | 30 s blocked on one write | Connection dropped without a close frame |
| Push notification rate per connection | 1000 pushes/second (burst 10000) | Connection closed with code 4429 |
| Idle WebSocket connection | 3600 s (1 hour) with no subscriptions and no messages | Connection closed with code 1001 |
| WebSocket connection maximum age | 24 hours (with ±10% jitter) | Connection closed with code 1012 |
Next steps
- Browse the datasets directory to see every dataset BlockVectra indexes.
- See the free plan and pricing to check what your account includes.
- Log in to the console to create an API key.
Last updated: