WebSockets
Subscriptions use the same key as HTTP, on the wss:// host:
wss://mainnet.prismrpc.co/v1/YOUR_API_KEYPrism 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
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
| Type | Fires on | Notes |
|---|---|---|
newHeads | Every new block header | The cheapest way to drive polling |
logs | Logs matching a filter | Takes an address and topics object |
newPendingTransactions | Transactions entering the mempool | High volume; counts heavily against your quota |
Filtered logs
socket.send(
JSON.stringify({
jsonrpc: '2.0',
id: 2,
method: 'eth_subscribe',
params: [
'logs',
{
address: '0x1234567890123456789012345678901234567890',
topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'],
},
],
})
);Unsubscribing
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:
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:
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
| Limit | Value |
|---|---|
| Concurrent sockets per key | 20 |
| Subscriptions per socket | 100 |
| Idle timeout with no subscriptions | 10 minutes |
| Maximum message size | 4 MB |
newPendingTransactions on a busy network can deliver thousands of messages a second. Subscribe to it only if you are consuming every message.
