WebSockets

Subscriptions for accounts, logs, signatures and slots over a persistent WebSocket connection.

The WebSocket endpoint speaks the standard Solana PubSub protocol: one connection carries subscriptions, and the node pushes a notification on each change. It is available from the Operator plan up.

Endpoints

wss://ws-newyork.constant-k.com:1337/ws?api-key=YOUR_API_KEY
wss://ws-frankfurt.constant-k.com:1337/ws?api-key=YOUR_API_KEY

The port is 1337 and the path is /ws. The key goes in the query string.

Plan limits

OperatorBlade RunnerNexus
Concurrent connections per key1050100
Subscriptions per connection1050100
New connections per key10 per 60 seconds10 per 60 seconds10 per 60 seconds
Requests per second (shared with HTTP RPC)50200500
blockSubscribe——✓

The Free plan does not include WebSockets. Connection and subscription counts are held by each region separately.

Both caps apply at once: a Nexus key can hold 100 connections, each carrying up to 100 subscriptions.

Cap reachedResult
Subscriptions per connectionThe subscribe call returns an error frame with code -32005 and the message Maximum subscriptions reached for your tier. The existing connection stays open.
Connections per key, or new connections per 60 secondsThe new connection is closed with code 1013 and a reason saying which.

Connection pacing applies on every plan. On Operator, opening 10 connections takes a full minute, so a long-lived connection avoids paying that cost again.

Subscription methods

The socket accepts subscription methods only, plus getHealth. There is no pass-through: any other method returns an error frame. Other calls go over HTTPS.

SubscribeNotification when
accountSubscribeAn account's lamports or data change
programSubscribeAny account owned by a program changes
logsSubscribeA transaction that mentions an address emits logs
signatureSubscribeA transaction reaches the commitment you asked for
slotSubscribeThe node processes a new slot
slotsUpdatesSubscribeA slot changes state inside the node
rootSubscribeA new slot is rooted
voteSubscribeThe node sees a new vote
blockSubscribeA block is confirmed or finalized (Nexus only)

Each …Subscribe method has a matching …Unsubscribe method that takes the subscription ID. Unsubscribing does not count against your rate limit or your subscription cap.

blockSubscribe

blockSubscribe needs the Nexus plan, and its options object must set "maxSupportedTransactionVersion": 1 as the JSON number 1. Without it, or with the string "1", the subscribe call fails with -32602. A notification whose block is null is a failure, not an empty block. See Transaction v1.

Keepalives

getHealth is the keepalive this endpoint accepts as a message. WebSocket protocol ping frames are a different thing and are answered with a pong. Where a client offers both, the protocol ping frames are the better choice.

@solana/kit and @solana/web3.js v1 send a JSON-RPC message named ping roughly every 5 seconds. ping is not a subscription method, so each one comes back as an error frame:

{ "jsonrpc": "2.0", "error": { "code": -32600, "message": "ping is not allowed over WebSocket. Use the RPC endpoint for non-subscription calls." }, "id": null }

The connection stays open, subscriptions keep delivering, and the message counts against nothing. An error handler that treats an unsolicited error as a broken socket has to ignore -32600 frames whose id is null.

Idle timeout

There is no short idle timeout. A connection is closed after 24 hours with no traffic in either direction. It is closed abruptly, with no close frame, so most clients report code 1006.

Any subscription that delivers, or a keepalive every 30 seconds, keeps a connection inside that window. Code 1006 means the connection has to be reopened and the subscriptions made again.

A wscat session

npx wscat -c "wss://ws-newyork.constant-k.com:1337/ws?api-key=YOUR_API_KEY"

A subscription:

{ "jsonrpc": "2.0", "id": 1, "method": "slotSubscribe" }

The first reply is the subscription ID. Notifications follow:

{ "jsonrpc": "2.0", "result": 0, "id": 1 }
{ "jsonrpc": "2.0", "method": "slotNotification", "params": { "result": { "parent": 447913192, "root": 447913161, "slot": 447913193 }, "subscription": 0 } }

slotUnsubscribe with the ID stops it:

{ "jsonrpc": "2.0", "id": 2, "method": "slotUnsubscribe", "params": [0] }

Client examples

@solana/kit returns each subscription as an async iterator. Aborting the signal unsubscribes.

account.ts
import { address, createSolanaRpcSubscriptions } from "@solana/kit";

const rpcSubscriptions = createSolanaRpcSubscriptions(
  `wss://ws-newyork.constant-k.com:1337/ws?api-key=${process.env.CK_API_KEY}`,
);

const abortController = new AbortController();

// Account changes for the USDC mint
const notifications = await rpcSubscriptions
  .accountNotifications(address("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), {
    commitment: "confirmed",
    encoding: "base64",
  })
  .subscribe({ abortSignal: abortController.signal });

for await (const { context, value } of notifications) {
  console.log(`slot ${context.slot}: ${value.lamports} lamports`);
}

// Call abortController.abort() to unsubscribe

Logs that mention a program:

logs.ts
import { address, createSolanaRpcSubscriptions } from "@solana/kit";

const rpcSubscriptions = createSolanaRpcSubscriptions(
  `wss://ws-newyork.constant-k.com:1337/ws?api-key=${process.env.CK_API_KEY}`,
);
const abortController = new AbortController();

const logs = await rpcSubscriptions
  .logsNotifications(
    { mentions: [address("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8")] },
    { commitment: "confirmed" },
  )
  .subscribe({ abortSignal: abortController.signal });

for await (const { value } of logs) {
  if (value.err) continue;
  console.log(value.signature, value.logs.length, "log lines");
}

Connection handling

  • A socket with no other traffic needs getHealth every 30 seconds or so.
  • A reconnect uses exponential backoff and then subscribes again. Subscription IDs belong to the old connection.
  • An unused subscription keeps sending data and uses bandwidth on both ends until it is cancelled.
  • One connection carries many subscriptions. More connections are only worth opening once the per-connection cap is reached.
  • One region per flow. A subscription on one region and HTTP reads on another can disagree by a slot or two.

Troubleshooting

SymptomCause
The connection closes right after it opens, often code 1006The key is missing or invalid, or your plan does not include WebSockets. The handshake succeeds first, so a bad key looks like a network fault
An error frame arrives every few seconds, but data keeps flowingYour client is sending its own ping message. See Keepalives
The connection closes with code 1013You are at your connection limit, or over 10 new connections per minute. The close reason names which
A new subscription returns -32005You are at the subscription cap for that connection, or over a rate limit. Another connection, or an unsubscribe, frees room
@solana/web3.js subscriptions never firewsEndpoint is not set, so the client does not connect to the WebSocket endpoint
blockSubscribe fails with -32602It needs the Nexus plan and "maxSupportedTransactionVersion": 1 as a number, not the string "1"
A call such as getBalance fails over the socketOnly subscriptions and getHealth are allowed. Other calls go over HTTPS

WebSockets and Kaldera

WebSockets suit apps that watch a handful of accounts or signatures. Kaldera gRPC covers high-volume streams, broad program-wide filters and lower latency. It adds richer filters, compression, replay after disconnects, and a dedicated lane per stream.

On this page