Solana Pub/Sub WebSocket — /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.
/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 tier; 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.Subscribe methods and tier requirementsobjectSolSubscribeParams (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.resultobjectErrors
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://api.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://ws.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);
}
});TypeScript SDK (@triport/sdk)
import { TriportClient } from "@triport/sdk";
const client = new TriportClient({ apiKey: process.env.TRIPORT_API_KEY });
const sol = client.solana.ws(); // opens wss://ws.triport.io/ws/sol
const sub = await sol.accountSubscribe(
"SysvarC1ock11111111111111111111111111111111",
{ commitment: "finalized", encoding: "jsonParsed" }
);
sub.on("notification", (n) => {
console.log("account changed at slot", n.context.slot);
});
// later
await sub.unsubscribe();Python (triport-sdk)
import asyncio
import os
from triport import TriportClient
async def main():
client = TriportClient(api_key=os.environ["TRIPORT_API_KEY"])
async with client.solana.ws() as sol: # wss://ws.triport.io/ws/sol
sub_id = await sol.account_subscribe(
"SysvarC1ock11111111111111111111111111111111",
commitment="finalized",
encoding="jsonParsed",
)
async for note in sol.notifications(sub_id):
print("account changed at slot", note["context"]["slot"])
asyncio.run(main())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.
processedis the lowest-latency but may be rolled back;finalizedis the strongest guarantee. The default isconfirmed. - Related channels: for the high-throughput provider-bridge stream see
/ws/sol-stream; for event-driven watchlist pushes see/ws/sol-watch. The streaming overview lists every channel: Streaming overview.