Geyser · Solana Streaming

Resilience & keepalive

A long-lived gRPC stream is not fire-and-forget. Networks drop, NATs time out, and a stream can go quiet without ever raising an error. Production consumers need a keepalive, a reconnect plan that recovers missed slots, and a way to avoid stalling the stream when their own handler falls behind.

Keepalive & idle detection

The nastiest failure is the silent one. A half-open stream — where your side still believes the socket is up but no bytes are arriving — delivers no data and fires no error event. You sit there waiting forever.

Defend on two layers. At the transport, turn on gRPC/HTTP2 keepalive so the channel probes the connection and surfaces a dead socket. At the application, run an idle-timeout watchdog: record the timestamp of every update and reconnect if nothing has arrived within your window. Pair the watchdog with a low-volume subscription — a slots or blocksMeta feed makes a cheap, steady heartbeat even when your real filters are quiet.

TypeScript
import Client, {
CommitmentLevel,
SubscribeRequest,
} from "@triton-one/yellowstone-grpc";
// gRPC/HTTP2 keepalive pings at the channel level
const client = new Client(
"https://geyser.therpc.io:443",
"<YOUR_API_KEY>",
{
"grpc.keepalive_time_ms": 10_000,
"grpc.keepalive_timeout_ms": 5_000,
"grpc.keepalive_permit_without_calls": 1,
}
);
const stream = await client.subscribe();
// app-level watchdog: if no update lands within the window, reconnect
let lastUpdateAt = Date.now();
const IDLE_TIMEOUT_MS = 30_000;
stream.on("data", (update) => {
lastUpdateAt = Date.now();
// ...handle update
});
setInterval(() => {
if (Date.now() - lastUpdateAt > IDLE_TIMEOUT_MS) {
console.warn("no updates — stream may be half-open, reconnecting");
stream.end();
// tear down and re-subscribe from your last processed slot
}
}, 5_000);

The proxy also exposes an app-level Ping you can write into the request channel; the server answers with a Pong. It is a request-channel heartbeat for streams that are otherwise silent.

TypeScript
// keep a quiet stream warm by writing a Ping into the request channel
// the server echoes it back as a Pong
const ping: SubscribeRequest = {
accounts: {},
slots: {},
transactions: {},
transactionsStatus: {},
blocks: {},
blocksMeta: {},
entry: {},
accountsDataSlice: [],
ping: { id: 1 },
};
setInterval(() => {
stream.write(ping, () => {});
}, 15_000);

Reconnect & gap recovery

A reconnect gives you a fresh live stream — it does not replay what you missed while you were gone. Closing that gap is on you, and the right tool depends on whether you hold an API key.

With a key, persist the last slot you fully processed and set fromSlot on the next request to replay from there. Before you trust that slot, call SubscribeReplayInfo to learn the earliest slot still available — if your gap predates the replay window, replay can't reach it.

TypeScript
// you persist the last slot you fully processed
const lastProcessedSlot = await store.getLastSlot();
// on reconnect, replay the gap — fromSlot requires an API key
const request: SubscribeRequest = {
accounts: {
raydium_pools: {
account: [],
owner: ["675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"],
filters: [],
nonemptyTxnSignature: undefined,
},
},
slots: {},
transactions: {},
transactionsStatus: {},
blocks: {},
blocksMeta: {},
entry: {},
accountsDataSlice: [],
commitment: CommitmentLevel.CONFIRMED,
fromSlot: lastProcessedSlot,
};
// check how far back replay is available before trusting fromSlot
const info = await client.subscribeReplayInfo();
if (lastProcessedSlot < info.firstAvailable) {
// gap is older than the replay window — backfill instead
// via getSignaturesForAddress on standard JSON-RPC
}

Without a key — or when the gap is older than the replay window — fromSlot isn't available. Fall back to tracking the last processed slot and backfilling the gap with getSignaturesForAddress on standard JSON-RPC, then resume the live stream from the head.

Backpressure

gRPC has flow control. If your consumer reads slower than the server pushes, the window fills and the stream stalls — and a stalled stream looks a lot like an idle one. Keep the read loop thin: pull updates off the wire and hand them to a queue or worker, do the heavy work elsewhere. Watch your lag by comparing the slot on the last update you processed against the chain head from a cheap slots feed. A lag that only grows means your consumer can't keep up, not that the network is slow.

Error handling

Handle gRPC status codes on the stream and retry transient ones with backoff. The first error many people hit is a permission one: keyless public requests may not set the commitment or fromSlot filters. Set either without a key and the proxy returns gRPC PermissionDenied:

Commitment filter is not supported for public requests. Consider registering and creating an API key.

The fix is an API key: it unlocks commitment selection and fromSlot replay in one move. Until then, drop those two filters and the stream runs at the default commitment, live-only.

Limits

Connection counts, packs, the trial, and crypto activation are all spelled out on the Geyser product page. This page covers the operational side — how to keep one connection healthy.