Solana WebSocket API (Pub/Sub) — /ws/sol
A single WebSocket endpoint for Solana JSON-RPC Pub/Sub: subscribe to account, signature, log, program, slot, root, block, vote, and slot-update events and receive server-pushed notifications in real time.
- Network
- Solana
- Scope
- sol.pubsub
- Auth
- x-token: $TRIPORT_API_KEY on the upgrade request (Authorization: Bearer also accepted), an auth frame, or ?api-key= query
- Rate limit
- Per-method tier gating — free / basic / pro (see Methods by plan)
Endpoint and plans: Solana RPC endpoint.
/ws/sol is one WebSocket connection that multiplexes all nine Solana JSON-RPC
Pub/Sub method families. Every frame is a JSON-RPC 2.0 message; the method
field disambiguates a subscribe request, an unsubscribe request, and a
server-pushed notification.
The lifecycle is: open the socket, send a *Subscribe request, and the server
replies with a SubscribeAck carrying an integer result — the
subscription id. From then on the server pushes *Notification frames whose
params.subscription matches that id, until you send the matching
*Unsubscribe (or the socket closes). Keep the subscription id — it is the only
handle you have to stop a stream.
Each method family carries a minimum tier requirement. accountSubscribe and
signatureSubscribe are available on the Free trial; logsSubscribe,
programSubscribe, slotSubscribe, and rootSubscribe require basic; and
blockSubscribe, voteSubscribe, and slotsUpdatesSubscribe require pro.
Subscribing to a method above your tier closes the connection with WS close code
4003 (forbidden) — see Errors.
Parameters
A subscribe frame is a JSON-RPC 2.0 request: {jsonrpc, id, method, params}.
jsonrpcstringrequired"2.0".idinteger | stringrequiredmethodstringrequiredparamsarrayrequiredaccountSubscribe / programSubscribe it is [<base58 pubkey>, <SolSubscribeParams>?]; for slot/root-style methods it is usually empty.SolSubscribeParams (config )objectcommitmentstringoptionalprocessed, confirmed, finalized. Defaults to confirmed.encodingstringoptionalbase58, base64, base64+zstd, or jsonParsed.Response
The first server frame is the SubscribeAck, whose result is the
subscription id:
Subsequent frames are server-pushed *Notification envelopes. An
accountNotification looks like:
{
"jsonrpc": "2.0",
"method": "accountNotification",
"params": {
"subscription": 24040,
"result": {
"context": { "slot": 348392041 },
"value": {
"lamports": 10000000000,
"owner": "11111111111111111111111111111111",
"data": ["", "base64"],
"executable": false,
"rentEpoch": 361
}
}
}
}result (on ack)integer*Unsubscribe.method (on notification)stringaccountNotification.params.subscriptionintegerparams.resultobjectMethods by plan
The lowest plan that may open each subscription; every higher plan includes it.
| Method | Plan from | Subscribes to |
|---|---|---|
accountSubscribe | Free (trial) | Changes to a single account, by base-58 pubkey. |
signatureSubscribe | Free (trial) | Confirmation status of a single transaction signature. |
logsSubscribe | Basic | Transaction logs (all, or filtered by mentioned account). |
programSubscribe | Basic | Changes to accounts owned by a program. |
slotSubscribe | Basic | Each new processed slot. |
rootSubscribe | Basic | Each new root the node sets. |
blockSubscribe | Pro | Full block notifications. |
voteSubscribe | Pro | New vote transactions observed by the node. |
slotsUpdatesSubscribe | Pro | Fine-grained per-slot update events. |
A subscribe above your plan is refused: the server sends an error frame with
required_tier and closes the socket with code 4003 (see Errors).
If a subscription stops delivering without a close, see why a subscription goes silent. For all Solana streaming options side by side, see the Solana streams overview; need filters across many accounts? Yellowstone gRPC. How Pub/Sub differs from Geyser-based gRPC: the Solana Geyser plugin explained.
Errors
Application errors are delivered as a final WSError frame immediately before
the socket closes with one of the canonical WebSocket close codes. The frame
mirrors the HTTP error envelope so SDKs can reuse the same error classes — see
the shared errors reference for the full envelope.
| Code | Meaning | When it happens |
|---|---|---|
4001 | unauthorized | Missing or invalid API key on the upgrade / auth frame. |
4003 | forbidden | Subscribing to a method above your tier (tier_insufficient) or an unknown method (method_unknown). The frame carries current_tier, required_tier, method, and an upgrade_url. |
4029 | rate_limited | Subscribe rate exceeded for your tier; the frame carries retry_after_sec and limit_rps. |
4030 | trial_expired | The account's trial has ended; the frame carries an upgrade_url. |
Example WSError frame sent before a 4003 close:
{
"error": "forbidden",
"message": "method requires a higher tier",
"current_tier": "free",
"required_tier": "pro",
"method": "blockSubscribe",
"category": "sol_ws_pubsub",
"upgrade_url": "https://triport.io/billing/upgrade?tier=pro"
}Examples
JavaScript (fetch)
The browser WebSocket API can't set custom headers, so authenticate with the
?api-key= query form:
const ws = new WebSocket(
`wss://triport.io/ws/sol?api-key=${process.env.TRIPORT_API_KEY}`
);
const subs = new Map(); // subscription id -> label
ws.addEventListener("open", () => {
ws.send(JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "accountSubscribe",
params: [
"SysvarC1ock11111111111111111111111111111111",
{ commitment: "finalized", encoding: "jsonParsed" },
],
}));
});
ws.addEventListener("message", (ev) => {
const msg = JSON.parse(ev.data);
if (msg.id === 1 && typeof msg.result === "number") {
subs.set(msg.result, "clock-account");
console.log("subscribed, id =", msg.result);
} else if (msg.method === "accountNotification") {
console.log("account changed at slot", msg.params.result.context.slot);
}
});Notes
- Hold onto the subscription id. The ack's
resultis the only handle for unsubscribing; route notifications byparams.subscriptionwhen you have several active subscriptions on one socket. - Multiple subscriptions, one socket. A single
/ws/solconnection can carry many subscriptions across different method families — open one socket per client, not one per subscription. - Tier gating is per method. A
freeconnection can runaccountSubscribeandsignatureSubscribeindefinitely, but ablockSubscribeon the same socket triggers a4003close. Checkrequired_tierin the error frame to build an upgrade prompt without a second API call. - Commitment trade-off.
processedarrives first but may be rolled back;finalizedis the strongest guarantee. The default isconfirmed. - Related channels: the other Solana connection options are the
Firehose (WebSocket) at
wss://triport.io/ws/sol-stream, gRPC (Yellowstone) athttps://grpc.triport.ioand the shred stream atwss://triport.io/ws/sol-preexec(all Pro). The streaming overview lists every channel: Streaming overview.