prismRPCDocs

WebSockets

Subscriptions use the same key as HTTP, on the wss:// host:

bash
wss://mainnet.prismrpc.co/v1/YOUR_API_KEY

Prism holds the upstream connection for you. If the provider behind your socket drops or degrades, Prism reconnects to a healthy one and replays your active subscriptions - your client keeps the same socket and its subscription IDs stay valid.

Subscribing

ts
const socket = new WebSocket('wss://mainnet.prismrpc.co/v1/YOUR_API_KEY');

socket.onopen = () => {
  socket.send(
    JSON.stringify({
      jsonrpc: '2.0',
      id: 1,
      method: 'eth_subscribe',
      params: ['newHeads'],
    })
  );
};

socket.onmessage = (event) => {
  const message = JSON.parse(event.data);

  if (message.id === 1) {
    console.log('subscription id', message.result);
    return;
  }

  if (message.method === 'eth_subscription') {
    console.log('new head', message.params.result.number);
  }
};

Subscription types

TypeFires onNotes
newHeadsEvery new block headerThe cheapest way to drive polling
logsLogs matching a filterTakes an address and topics object
newPendingTransactionsTransactions entering the mempoolHigh volume; counts heavily against your quota

Filtered logs

ts
socket.send(
  JSON.stringify({
    jsonrpc: '2.0',
    id: 2,
    method: 'eth_subscribe',
    params: [
      'logs',
      {
        address: '0x1234567890123456789012345678901234567890',
        topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'],
      },
    ],
  })
);

Unsubscribing

ts
socket.send(
  JSON.stringify({
    jsonrpc: '2.0',
    id: 3,
    method: 'eth_unsubscribe',
    params: [subscriptionId],
  })
);

Sockets that go idle for 10 minutes with no active subscriptions are closed. Send any request, or keep at least one subscription open, to hold the connection.

Reconnection and gaps

Prism's upstream reconnection is transparent, but it is not a guarantee of continuity. A failover takes a moment, and events published during it are not replayed to you - the subscription resumes at the current head.

For anything where a missed event is a correctness problem - indexers, accounting, settlement - treat the subscription as a trigger and reconcile against eth_getLogs:

ts
let lastProcessed = await getCheckpoint();

socket.onmessage = async (event) => {
  const message = JSON.parse(event.data);
  if (message.method !== 'eth_subscription') return;

  const head = BigInt(message.params.result.number);

  // Backfill anything between the checkpoint and this head, so a gap during a
  // reconnect is closed rather than skipped.
  const logs = await publicClient.getLogs({
    address: CONTRACT,
    fromBlock: lastProcessed + 1n,
    toBlock: head,
  });

  await process(logs);
  lastProcessed = head;
  await saveCheckpoint(head);
};

Client-side reconnection

Your own socket can still close - a laptop sleeps, a load balancer recycles, a mobile network changes. Reconnect with backoff and resubscribe:

ts
function connect(attempt = 0) {
  const socket = new WebSocket('wss://mainnet.prismrpc.co/v1/YOUR_API_KEY');

  socket.onopen = () => {
    attempt = 0;
    resubscribeAll(socket);
  };

  socket.onclose = () => {
    const delay = Math.min(1000 * 2 ** attempt, 30_000);
    setTimeout(() => connect(attempt + 1), delay + Math.random() * 250);
  };

  return socket;
}

Limits

LimitValue
Concurrent sockets per key20
Subscriptions per socket100
Idle timeout with no subscriptions10 minutes
Maximum message size4 MB

newPendingTransactions on a busy network can deliver thousands of messages a second. Subscribe to it only if you are consuming every message.