WebSocket Overview
Connecting and using WebSocket events
WebSocket endpoint: ws://<node-ip>:21000/events
REST and WebSocket share port 21000. For non-localhost or unsecured LAN deployments, TLS termination via reverse proxy is strongly recommended:
wss://<node-ip>/events
Connection
Section titled “Connection”The Sec-WebSocket-Protocol header is required. Connections without it are rejected at
upgrade.
Sec-WebSocket-Protocol: blockd-v1Example using websocat:
websocat --protocol blockd-v1 ws://<node-ip>:21000/eventsAuthentication
Section titled “Authentication”Authentication is required. The first message sent after connect must be the auth message:
{"auth": {"token": "REPLACE_ME_secret_token"}}Server response on success:
{"event": "auth", "result": "success"}On failure:
{"event": "auth", "result": "invalid_key"}The connection is closed after an invalid key. Any message sent before auth is rejected with an error event.
Ping / Pong
Section titled “Ping / Pong”WebSocket ping/pong operates at the transport level. Incoming Ping frames are automatically responded to by blockd with a Pong.
blockd also sends keepalive Pings to the client at 30 second intervals. Clients should respond with a Pong to maintain the connection. Most WebSocket client libraries handle this automatically.
Event Envelope
Section titled “Event Envelope”All server-sent messages share a common envelope. The event field is always present and
identifies the message type:
{"event": "<event_name>", ...}The WebSocket API is not request/response. Subscriptions are fire-and-forget commands. Events arrive asynchronously. Use the query interface to inspect state.
Subscriptions
Section titled “Subscriptions”List of all subscriptions:
| Subscription | Description |
|---|---|
server.health | Periodic server health snapshots, including IBD progress |
workflow.progress | Step-level progress for running workflows |
pubkeys.activity | Activity on a tracked wallet’s addresses |
block.activity | Block tip changes, including reorg detection |
core.hashblock | New block hash from bitcoind |
core.hashtx | New transaction hash from bitcoind |
core.rawblock | Raw block bytes from bitcoind |
core.rawtx | Raw transaction bytes from bitcoind |
core.sequence | Mempool and chain sequence events |
Subscribe and Unsubscribe
Section titled “Subscribe and Unsubscribe”A subscriptions message contains an array of action objects. Each action object uses
either subscribe or unsubscribe as its key. Both types can appear in the same message
and are all applied atomically. No acknowledgment is sent.
Subscribe:
{ "subscriptions": [ {"subscribe": "block.activity"}, {"subscribe": "pubkeys.activity", "args": {"pubkeys_set": "testnet/wallet-1"}} ]}Unsubscribe:
{ "subscriptions": [ {"unsubscribe": "pubkeys.activity", "args": {"pubkeys_set": "testnet/wallet-2"}} ]}Mixed subscribe and unsubscribe in a single message:
{ "subscriptions": [ {"subscribe": "pubkeys.activity", "args": {"pubkeys_set": "testnet/wallet-1"}}, {"unsubscribe": "pubkeys.activity", "args": {"pubkeys_set": "testnet/wallet-2"}} ]}A malformed message returns an error event — no actions in a malformed message are applied.
Unsubscribe all
Section titled “Unsubscribe all”Send an empty subscriptions array to cancel all active subscriptions:
{"subscriptions": []}Queries
Section titled “Queries”subscriptions
Section titled “subscriptions”Send a query to get current active subscriptions:
{"query": "subscriptions"}Response:
{ "event": "subscriptions", "subscriptions": [ { "subscribe": "server.health", "args": {"interval": 30, "tip": true, "detailed": true} } ]}A development utility for verifying the connection and auth stack. Send any data and blockd echoes it back as an event:
→ {"echo": {"data": "hello"}}← {"event": "echo", "echo": {"data": "hello"}}Useful for confirming the full connection — protocol header, auth, and message handling — is working correctly before implementing real subscriptions.
Errors
Section titled “Errors”Error events are sent when a client message fails, or when an asynchronous error occurs during workflow execution or subscription handling.
{ "event": "error", "error": { "kind": "subscription", "message": "unknown topic: core.badtopic" }}Error fields
Section titled “Error fields”| Field | Type | Description |
|---|---|---|
kind | string | Error category — see kinds table below |
reason | string | Machine-readable detail within the kind. Stable snake_case string |
message | string | Human-readable description. Do not parse programmatically |
Optional fields are omitted when not applicable.
Error kinds
Section titled “Error kinds”| Kind | Description |
|---|---|
authorization | Authentication or permission failure |
query | A client query could not be fulfilled |
workflow | An async error occurred during workflow execution |
subscription | A subscription message was malformed or referenced an unknown topic |
core | Error originating from the bitcoind subsystem |
indexer | Error originating from the indexer subsystem |
internal | Unexpected internal server error |
Examples
Section titled “Examples”Malformed subscription message:
→ {"subscriptions" [{"subscribe": "core.hashblock"}]}← {"event": "error", "error": {"kind": "subscription", "message": "invalid message format: expected `:` at line 2 column 19"}}Async workflow failure:
{ "event": "error", "error": { "kind": "workflow", "reason": "workflow_failed", "message": "indexer unavailable" }}Connection Lifecycle
Section titled “Connection Lifecycle”client blockd | | |--- WS upgrade | | Sec-WebSocket-Protocol: | | blockd-v1 ---------------------->| |<-- 101 Switching Protocols ---------| | Sec-WebSocket-Protocol: | | blockd-v1 -----------------------| | | |--- {"auth":{"token":"..."}} ------->| |<-- {"event":"auth", | | "result":"success"} ------------| | | |--- {"subscriptions":[...]} -------->| | | |<-- events... -----------------------|