Ethras Blog
Recover Ethereum WebSocket Logs Without Leaving Gaps
Use subscriptions for timely notifications and a reconciled historical cursor for completeness. Reconnect, backfill, deduplicate, and handle replaced logs explicitly.
A live event feed can look healthy while its history is incomplete. The socket reconnects, fresh logs start arriving, and the UI resumes moving. Events emitted during the outage may still be missing, and an earlier event may have been removed by a reorganization while the consumer was offline.
For teams building activity feeds or event-driven services, timely delivery and historical completeness need separate mechanisms. Use the subscription to learn quickly that something changed. Use persisted progress and repeatable reads to establish what the application has actually processed.
Treat the connection as a notification channel
Geth's subscription documentation states that notifications concern current events, subscriptions belong to their connection, and a slow consumer can cause the connection to close. Reopening a socket does not resume the previous subscription or supply a durable replay cursor.
This is a valid logs subscription request. The address is synthetic; replace it with your intended contract and confirm subscription support on your selected network and endpoint:
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_subscribe",
"params": [
"logs",
{
"address": "0x0000000000000000000000000000000000000001",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
]
}
]
}
Match the successful response to request ID 1, then route subsequent notifications using the returned subscription ID. Store the actual ID only for that connection. It is not an application checkpoint.
For reorgs, Geth describes previously emitted logs being sent again with removed: true, and transactions may produce repeated notifications. Your handler should support these outcomes, but its recovery design must also work when a removal notification never arrives.
Maintain two different positions
Keep an observed position for live responsiveness and a reconciled position for durable completeness. The highest received notification belongs to the first category. A completed, validated historical range belongs to the second.
The durable record should identify the network, filter version, completed block number, and completed block hash. Persist it with the indexed data. If an application tracks several contracts or filters independently, make that partitioning explicit so one stream cannot advance another stream's cursor.
For the UI, recent notifications can appear as provisional items. A background worker can later promote or replace them under the application's chain-progress policy. For an irreversible business action, require the appropriate reconciled state rather than treating socket arrival as approval.
Close the reconnect race deliberately
A useful recovery sequence begins by installing the notification handler, then establishing a new subscription and temporarily buffering notifications. After its acknowledgement, resolve a concrete HTTP target header on the same network. Then query history from your durable recovery position through that target, reconcile block identities, and merge overlapping buffered events through the same idempotent ingestion path.
The JSON-RPC log query supplies the historical request interface. Chunk that backfill according to the target endpoint's limits and retained history. A WebSocket capability does not imply unlimited historical queries.
The following is orchestration pseudocode, not a complete client implementation:
load durable checkpoint and recent block identities
connect and install a handler with a fixed notification buffer
establish a fresh logs subscription and retain matching notifications
resolve a concrete catch-up header using HTTP
validate the stored chain view and find any required rollback
backfill contiguous ranges through the catch-up target
merge buffered events using the same idempotent ingestion path
continue periodic historical reconciliation while live events arrive
Subscribing before backfill creates overlap on purpose. Deduplicate log instances by network, block hash, and log index. Apply removal or replacement state transitions separately; ignoring every message with an already seen identity would also ignore a later removal.
Do not release a buffer merely because its largest block number exceeds the catch-up target. Drain it through an ordered reconciler that checks parent relationships and fills missing ranges. Use one serial ingestion path for buffer draining and live consumption so the hand-over cannot skip arriving notifications. In-flight notifications and HTTP reads can reflect different moments, even when both connections are working.
Revisit the history you saw before disconnecting
Starting at the highest observed block plus one is insufficient. A reorganization can replace blocks already stored while the connection is down. EIP-234's rationale specifically discusses losing removal notifications during a disconnected reorg.
On recovery, compare retained block identities with the current canonical view and locate a common ancestor. Roll back provisional derived records above it, then ingest the replacement branch. If your application retains only a bounded recent window, define what happens when recovery needs an older ancestor: pause and rebuild or invoke an explicit recovery procedure. Do not treat the configured window as a protocol guarantee.
The overlap is about block identity as well as duplicate logs. A range with no matching events still needs validation and progress tracking. Otherwise quiet periods leave the application unable to distinguish an empty history from a missing query.
Bound memory and make recovery observable
The browser WebSocket API does not apply automatic backpressure. Put a bound on your own incoming queue and keep expensive decoding or database work away from an unbounded message callback.
If the buffer overflows during catch-up, discard the incomplete recovery attempt and restart from durable progress with controlled load. A reconnect delay with jitter can reduce repeated pressure, while periodic HTTP reconciliation repairs missed ranges even when no disconnect was reported.
Monitor durable lag, oldest buffered block, queue size, backfill failures, rollback depth, and time since the last successful reconciliation. A connected indicator answers whether the transport is open. These measurements answer whether the event history is keeping up.
Use the WebSocket topic guide for connection syntax. Build the consumer around a stronger operational invariant: after interruption, it can explain the complete range it has reconciled and repeat the missing work without duplicating its effects.
Share this article
Share on XReview your account and post on X. Opening X or copying text does not send a post.