TriportRPC

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.

Solanasol.pubsubPer-method tier gating — free / basic / pro (see [Parameters](#parameters))

/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
Must be "2.0".
idinteger | stringrequired
Client-chosen request id; echoed back on the ack.
methodstringrequired
One of the nine subscribe methods (see the table below).
paramsarrayrequired
Positional params array — shape depends on the method. For accountSubscribe / programSubscribe it is [<base58 pubkey>, <SolSubscribeParams>?]; for slot/root-style methods it is usually empty.
Subscribe methods and tier requirementsobject
SolSubscribeParams (config )object
commitmentstringoptional
One of processed, confirmed, finalized. Defaults to confirmed.
encodingstringoptional
Account data encoding: base58, 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
The subscription id. Pass it to the matching *Unsubscribe.
method (on notification)string
The notification method, e.g. accountNotification.
params.subscriptioninteger
The subscription id this notification belongs to.
params.resultobject
The event payload — shape is per-method (account state, signature status, log batch, slot info, etc.).

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.

CodeMeaningWhen it happens
4001unauthorizedMissing or invalid API key on the upgrade / auth frame.
4003forbiddenSubscribing 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.
4029rate_limitedSubscribe rate exceeded for your tier; the frame carries retry_after_sec and limit_rps.
4030trial_expiredThe 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 result is the only handle for unsubscribing; route notifications by params.subscription when you have several active subscriptions on one socket.
  • Multiple subscriptions, one socket. A single /ws/sol connection can carry many subscriptions across different method families — open one socket per client, not one per subscription.
  • Tier gating is per method. A free connection can run accountSubscribe and signatureSubscribe indefinitely, but a blockSubscribe on the same socket triggers a 4003 close. Check required_tier in the error frame to build an upgrade prompt without a second API call.
  • Commitment trade-off. processed is the lowest-latency but may be rolled back; finalized is the strongest guarantee. The default is confirmed.
  • 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.