Solana - WebSocket

Learn how to use Tatum's Solana WebSocket endpoint to subscribe to real-time on-chain events including account updates, transaction logs, signature confirmations, and block notifications.

Solana's WebSocket PubSub interface pushes real-time notifications to your application over a persistent connection, with no polling required.

With Tatum's Solana WebSocket endpoint, you can subscribe to:


Solana Mainnet WebSocket endpoint

InterfaceEndpoint
WSS (Mainnet)wss://solana-mainnet.gateway.tatum.io

For JSON-RPC (HTTP) and the full network list, see Solana RPC.


Getting started

Environment setup

This guide uses Node.js 22+ with the native WebSocket API and TypeScript via tsx.

  1. Install Node.js (version 22+):

    brew install node
    node -v
  2. Create your project:

    mkdir tatum-wss-test
    cd tatum-wss-test
    npm init -y
    npm install -D tsx typescript @types/node
  3. Get your API key from the Tatum Dashboard.

Connection and authentication

The API key is passed as a path segment on the WebSocket URL.

const API_KEY = "YOUR_API_KEY";
const ENDPOINT = `wss://solana-mainnet.gateway.tatum.io/${API_KEY}`;

const ws = new WebSocket(ENDPOINT);

Quick start checklist

Before opening subscriptions, confirm:

  • Your WebSocket connection opens without error
  • You receive a numeric subscription ID in the server response after the first subscribe call
  • Your service can hold long-lived outbound connections open
  • You have a reconnect strategy — WebSocket connections drop; assume it and rebuild on reconnect
  • You send a ping every 60 seconds to prevent the connection from timing out due to inactivity

End-to-end example

The following script connects to the endpoint and subscribes to log notifications for all finalized non-vote transactions. It is the fastest way to confirm your credentials and connection are working.

const API_KEY = "YOUR_API_KEY";
const ENDPOINT = `wss://solana-mainnet.gateway.tatum.io/${API_KEY}`;

const ws = new WebSocket(ENDPOINT);

ws.onopen = () => {
  console.log("connected");

  ws.send(JSON.stringify({
    jsonrpc: "2.0",
    id: 1,
    method: "logsSubscribe",
    params: [
      "all",
      { commitment: "finalized" }
    ]
  }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data as string);

  if (msg.result !== undefined) {
    console.log("subscription id:", msg.result);
    return;
  }

  if (msg.method === "logsNotification") {
    const { signature, err, logs } = msg.params.result.value;
    console.log("tx:", signature, "| status:", err ? "failed" : "ok");
    console.log("logs:", logs);
  }
};

ws.onerror = (err) => console.error("error:", err);
ws.onclose = () => console.log("connection closed");

Run it with:

npx tsx index.ts
📘

Note

tsx runs TypeScript files directly with no tsconfig.json required.

🚧

Attention

This example streams live Solana transactions and will keep running until you stop it. On macOS, press Ctrl+C in the terminal to stop the stream once you have confirmed it is working.


Example requests

Note: These are minimal examples. For production, implement reconnection with exponential backoff, track subscription IDs, and call the matching unsubscribe method when a subscription is no longer needed.

accountSubscribe

Subscribe to notifications whenever a specific account's lamports or data change.

ws.send(JSON.stringify({
  jsonrpc: "2.0",
  id: 1,
  method: "accountSubscribe",
  params: [
    "CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12",
    {
      encoding: "jsonParsed",
      commitment: "finalized"
    }
  ]
}));

Each notification arrives as accountNotification and includes the slot, lamport balance, owner program, and full account data in the requested encoding. Use base64 if you need raw bytes.

To unsubscribe, call accountUnsubscribe with the subscription ID returned by the server:

ws.send(JSON.stringify({
  jsonrpc: "2.0",
  id: 2,
  method: "accountUnsubscribe",
  params: [SUBSCRIPTION_ID]
}));

logsSubscribe

Subscribe to transaction log output. Filter by transaction type or by a specific address.

All non-vote transactions:

ws.send(JSON.stringify({
  jsonrpc: "2.0",
  id: 1,
  method: "logsSubscribe",
  params: [
    "all",
    { commitment: "finalized" }
  ]
}));

Transactions mentioning a specific address:

ws.send(JSON.stringify({
  jsonrpc: "2.0",
  id: 1,
  method: "logsSubscribe",
  params: [
    { mentions: ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"] },
    { commitment: "confirmed" }
  ]
}));

Available filter values:

FilterDescription
"all"All transactions except simple vote transactions
"allWithVotes"All transactions, including vote transactions
{ "mentions": ["<pubkey>"] }Transactions mentioning a single address (exactly one address per call)

Notifications arrive as logsNotification and include the transaction signature, slot, error status, and raw log lines from the BPF runtime.


signatureSubscribe

Subscribe to confirmation status for a submitted transaction. The subscription closes automatically once the terminal confirmation is delivered.

ws.send(JSON.stringify({
  jsonrpc: "2.0",
  id: 1,
  method: "signatureSubscribe",
  params: [
    "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQo",
    {
      commitment: "finalized",
      enableReceivedNotification: false
    }
  ]
}));

Set enableReceivedNotification: true to receive an early receivedSignature notification when the RPC node first sees the transaction, before confirmation.


programSubscribe

Subscribe to changes in all accounts owned by a specific program.

ws.send(JSON.stringify({
  jsonrpc: "2.0",
  id: 1,
  method: "programSubscribe",
  params: [
    "11111111111111111111111111111111",
    {
      encoding: "jsonParsed",
      commitment: "finalized"
    }
  ]
}));

Notifications arrive as programNotification. Use specific program addresses rather than broad ones to keep notification volume manageable.


slotSubscribe

Subscribe to slot progression for liveness monitoring and network clock synchronization.

ws.send(JSON.stringify({
  jsonrpc: "2.0",
  id: 1,
  method: "slotSubscribe",
  params: []
}));

Notifications arrive as slotNotification and include the slot number, parent slot, and root slot.


rootSubscribe

Subscribe to root slot changes. The root is the slot that has been finalized by the cluster.

ws.send(JSON.stringify({
  jsonrpc: "2.0",
  id: 1,
  method: "rootSubscribe",
  params: []
}));

blockSubscribe

Subscribe to new block notifications.

ws.send(JSON.stringify({
  jsonrpc: "2.0",
  id: 1,
  method: "blockSubscribe",
  params: [
    "all",
    {
      commitment: "confirmed",
      encoding: "jsonParsed",
      transactionDetails: "full",
      showRewards: false
    }
  ]
}));

Commitment levels

Most subscription methods accept an optional commitment parameter. If omitted, finalized is the default.

CommitmentDescription
processedThe node has received the transaction. Fastest, lowest certainty.
confirmedThe transaction has been voted on by a supermajority of the cluster.
finalizedThe transaction is finalized and will not be rolled back. Default.

Troubleshooting

SymptomCheck
Connection drops immediatelyVerify your API key is valid and included in the URL
No subscription ID returnedThe subscribe call was malformed; check the JSON structure
No notifications receivedWiden your filter or lower commitment to confirmed
Frequent disconnectsAdd reconnect logic with exponential backoff and jitter
High notification volumeNarrow your filter — use mentions instead of all, or move to Solana gRPC for high-throughput use cases

WebSocket vs gRPC

WebSocket is well suited for: signature confirmation polling, wallet UI balance updates, slot heartbeats, and low-volume program monitoring with tight filters.

For high-throughput use cases (multiple parallel subscriptions, trading infrastructure, indexers), consider Solana Yellowstone gRPC, which delivers typed, structured data without the parsing overhead of raw log lines.