> ## Documentation Index
> Fetch the complete documentation index at: https://docs.solanatracker.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Solana RPC WebSocket subscriptions

> Subscribe to Solana account changes, handle subscription IDs, unsubscribe cleanly, and recover after a WebSocket disconnect.

Use WebSocket subscriptions for changes after a connection is established. Fetch an HTTP snapshot when you also need the current state; a subscription is not a history query.

## Connect and subscribe

Use Node.js 20 or later. Install `ws`, set an RPC API key, and choose an account that changes regularly:

```bash theme={null}
npm install ws
export SOLANA_RPC_API_KEY="YOUR_API_KEY"
export ACCOUNT_ADDRESS="ACCOUNT_PUBLIC_KEY"
```

Save this as `accounts.mjs`, run `node accounts.mjs`, and stop with Ctrl+C. The example unsubscribes before closing when a subscription ID is available.

```javascript accounts.mjs theme={null}
import WebSocket from "ws";

const apiKey = process.env.SOLANA_RPC_API_KEY;
const account = process.env.ACCOUNT_ADDRESS;
if (!apiKey || !account) {
  throw new Error("Set SOLANA_RPC_API_KEY and ACCOUNT_ADDRESS");
}
const endpoint = new URL("wss://rpc-data.solanatracker.io/");
endpoint.searchParams.set("api_key", apiKey);
const socket = new WebSocket(endpoint);
let subscriptionId;
let stopping = false;
let closeTimer;

function unsubscribe() {
  socket.send(JSON.stringify({
    jsonrpc: "2.0", id: 2,
    method: "accountUnsubscribe", params: [subscriptionId],
  }));
}

socket.on("open", () => {
  socket.send(JSON.stringify({
    jsonrpc: "2.0", id: 1, method: "accountSubscribe",
    params: [account, { encoding: "base64", commitment: "confirmed" }],
  }));
});

socket.on("message", raw => {
  let message;
  try { message = JSON.parse(raw.toString()); }
  catch { console.error("Invalid JSON received"); return; }
  if (message.error) {
    console.error("RPC error", message.error);
    socket.close();
    return;
  }
  if (message.id === 1) {
    subscriptionId = message.result;
    console.log("Subscribed", subscriptionId);
    if (stopping) unsubscribe();
  } else if (message.id === 2) {
    console.log("Unsubscribed", message.result);
    socket.close();
  } else if (message.method === "accountNotification" &&
             message.params.subscription === subscriptionId) {
    const { context, value } = message.params.result;
    console.log({ slot: context.slot, lamports: value.lamports });
  }
});

socket.on("error", error => console.error("WebSocket error", error.message));
socket.on("close", () => {
  clearTimeout(closeTimer);
  console.log("Connection closed");
});
process.once("SIGINT", () => {
  stopping = true;
  closeTimer = setTimeout(() => socket.terminate(), 3_000);
  if (socket.readyState === WebSocket.OPEN && subscriptionId !== undefined) {
    unsubscribe();
  } else if (socket.readyState !== WebSocket.OPEN) {
    socket.terminate();
  }
});
```

A successful subscribe response confirms the subscription, not an account change. No notifications arrive until the account changes at the requested commitment.

## Request IDs and subscription IDs

The `id` you send identifies a request and its response. The server returns a separate subscription ID in `result`. Notifications carry that ID in `params.subscription`; pass it to the matching unsubscribe method on the same connection.

## Choose a subscription

* [accountSubscribe](/solana-rpc/websockets/accountsubscribe): changes to one account's lamports or data.
* [programSubscribe](/solana-rpc/websockets/programsubscribe): writes to accounts owned by a program.
* [logsSubscribe](/solana-rpc/websockets/logssubscribe): transaction logs matching the filter.
* [signatureSubscribe](/solana-rpc/websockets/signaturesubscribe): a signature reaching the requested commitment; this subscription ends after the terminal notification.
* [slotSubscribe](/solana-rpc/websockets/slotsubscribe): processed slot updates.

See the [Solana WebSocket reference](https://solana.com/docs/rpc/websocket) for standard notification semantics. Shredstream delivers transactions before execution metadata is available. Treat them as provisional and confirm their outcome through RPC.

## Recover after a disconnect

Reconnect with bounded backoff and jitter, then send the subscription requests again. IDs from the old connection cannot be reused. Refetch current account state over HTTP and reconcile it with buffered notifications using context slots. Recover transaction history separately when every event matters; reconnecting does not replay missed updates.

Keep a bounded processing queue and monitor its size. If the consumer falls behind, reduce the subscription scope or move expensive decoding out of the message callback. Check your plan's [connection limits](/solana-rpc/credits-and-rate-limits).
