Update balances, send notifications, or start jobs as new blocks and matching events arrive.
This guide subscribes to USDC Transfer logs over WebSocket, then shows how to fill the gap with
eth_getLogs when the connection drops, because a new subscription only delivers new events.
Prerequisites
- An RPC API key.
- WebSocket access for the target chain.
- A WebSocket client. Use route-token authentication when the client cannot send custom headers.
- Durable progress tracking for the last processed block and log identity.
Subscribe to logs
This request subscribes to USDC Transfer logs over the Ethereum mainnet WebSocket endpoint. The
endpoint uses route-token authentication, so the URL contains your API key; redact it from logs,
metrics labels, and support output.
Prerequisites
Install Node.js.
Install tsx and ws.
npm install tsx wsSet GRAPHOPS_API_KEY to your GraphOps API key.
TypeScript
import WebSocket from "ws";
const socket = new WebSocket(
`wss://rpc.graphops.xyz/v1/evm/1/${process.env.GRAPHOPS_API_KEY}`,
);
socket.once("error", (error) => { throw error; });
socket.once("open", () => {
socket.send(
JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_subscribe",
params: [
"logs",
{
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
// keccak256("Transfer(address,address,uint256)"), the Transfer event topic.
topics: [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
],
},
],
}),
);
});
let done = false; // This demonstration prints one notification.
socket.on("message", (data) => {
const message = JSON.parse(data.toString());
if (message.error) throw new Error(JSON.stringify(message.error));
// The first reply carries the subscription id; each log then arrives as an
// eth_subscription notification.
if (message.method !== "eth_subscription" || done) return;
done = true;
console.log(JSON.stringify(message));
socket.close();
});
Output
{ "jsonrpc": "2.0", "method": "eth_subscription", "params": { "result": { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000055fe002aeff02f77364de339a1292923a15844b8", "0x00000000000000000000000028c5b0445d0728bc25f143f8eba5c5539fae151a" ], "data": "0x00000000000000000000000000000000000000000000000000002d79883e0a60",Transfer amount: 50,000,000,060,000 base units, 50,000,000.06 USDC "blockHash": "0x966a9387557f8f036fcd29c10298b56abfd6b20fa2805895f3b1c57b4d1b4027", "blockNumber": "0x1908d64",Block in decimal: 26,250,596 "blockTimestamp": "0x6ada741b", "transactionHash": "0x5b326296f0207555ce928a8e648432ceaa9ee5d218490a015f0b2651b1b94d7b", "transactionIndex": "0x7", "logIndex": "0x5", "removed": false }, "subscription": "0x0462c550a95f5d8bfd792b5a4c5c09ec"Subscription ID returned by eth_subscribe }}The program prints one notification, then closes the subscription. The subscription ID in
params.subscription identifies the subscription; notifications carry no request id.
Use a client library
Client libraries manage the subscription for you over the route-token WebSocket endpoint: viem with
watchEvent, ethers with WebSocketProvider, and web3.py with its asynchronous WebSocket provider.
Prerequisites
Install Node.js.
Install tsx and viem.
npm install tsx viemSet GRAPHOPS_API_KEY to your GraphOps API key.
TypeScript
import { createPublicClient, parseAbiItem, webSocket } from "viem";
import { mainnet } from "viem/chains";
const client = createPublicClient({
chain: mainnet,
transport: webSocket(`wss://rpc.graphops.xyz/v1/evm/1/${process.env.GRAPHOPS_API_KEY}`),
});
let done = false; // This demonstration stops after the first log.
const unwatch = client.watchEvent({
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
event: parseAbiItem(
"event Transfer(address indexed from, address indexed to, uint256 value)",
),
// Over WebSocket, viem delivers each eth_subscription log as a one-item array.
onLogs: async (logs) => {
if (done) return;
done = true;
console.log(
JSON.stringify(logs, (_, value) =>
typeof value === "bigint" ? value.toString() : value,
),
);
unwatch();
// Close the socket so the process can exit.
const socket = await client.transport.getRpcClient();
socket.close();
},
onError: (error) => { throw error; },
});
Output
[ { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000000000000004444c5dc75cb358380d2e3de08a90", "0x0000000000000000000000000000000aa232009084bd71a5797d089aa4edfad4" ], "data": "0x0000000000000000000000000000000000000000000000000000000000000415", "blockHash": "0xb2a1be36fcf1f9b5f6bae9541ac4f9b6a4b0d91befd0b146e712242e4db8ded2", "blockNumber": "26250597",Store as your last processed block "blockTimestamp": "1792701479", "transactionHash": "0x1ade868b1c54863c059938e692aadd716e5c6c79e5d7b2a279760c748d01619c", "transactionIndex": 0, "logIndex": 0, "removed": false,true when a reorg reverses this log "args": { "from": "0x000000000004444c5dc75cB358380D2e3dE08A90", "to": "0x0000000aa232009084Bd71A5797d089AA4Edfad4", "value": "1045" }, "eventName": "Transfer" }]Supported subscriptions
| Subscription | Use it for |
|---|---|
newHeads | New block headers. |
logs | Logs matching an address and topic filter. |
newPendingTransactions | Pending transaction hashes when available for the network. |
syncing | Node sync status updates. |
Recover after a disconnect
- Store the last processed block and log identity as you handle each notification.
- Reconnect with exponential backoff after a disconnect.
- Resubscribe with the same filter.
- Backfill with
eth_getLogsfrom the block after the last one you processed up to the first block the new subscription delivers. - Deduplicate by
(blockHash, transactionHash, logIndex)where the backfill and the live stream overlap. - Treat logs with
removed: trueas reversals. See Retries and reorgs.
The backfill is an ordinary HTTPS request. This one reads a single block of USDC Transfer logs:
Prerequisites
Set GRAPHOPS_API_KEY to your GraphOps API key.
curl
# Block numbers are hex quantities: 0x112a882-0x112a887 is blocks 18,000,002-18,000,007.
# The topic is keccak256("Transfer(address,address,uint256)"), the Transfer event signature.
curl --fail-with-body --silent --show-error "https://rpc.graphops.xyz/v1/evm/1" \
-H "Authorization: Bearer ${GRAPHOPS_API_KEY}" \
-H "Content-Type: application/json" \
--data '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getLogs",
"params": [
{
"fromBlock": "0x112a882",
"toBlock": "0x112a887",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
]
}
]
}'
Output
{ "jsonrpc": "2.0", "id": 1, "result": [Every matching log in the requested block range { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",USDC contract "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",Transfer event signature "0x000000000000000000000000e673bda19a529d9ff13aeddae0e08c0fda8b5488", "0x00000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f5640" ], "data": "0x00000000000000000000000000000000000000000000000000000000401332c0",Transfer amount: 1,075,000,000 base units, 1,075 USDC "blockHash": "0x5b94c39ad7eecbf043f62a4a76c5abee87195207df98ae16eb3f289b2267f141", "blockNumber": "0x112a882",Block in decimal: 18,000,002 "blockTimestamp": "0x64ea26a7", "transactionHash": "0x29ec08dc5eed0e1cd411b29c52607661155087ffa107bae7f5e4a79f5306be1d", "transactionIndex": "0x2b", "logIndex": "0xac", "removed": false }, { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000a40bb1c47f6dd27142a2bd7c93bfa98db9d1f5c5", "0x000000000000000000000000b4e16d0168e52d35cacd2c6185b44281ec28c9dc" ], "data": "0x0000000000000000000000000000000000000000000000000000000011348e5d", "blockHash": "0x5b94c39ad7eecbf043f62a4a76c5abee87195207df98ae16eb3f289b2267f141", "blockNumber": "0x112a882", "blockTimestamp": "0x64ea26a7", "transactionHash": "0xa8b07b56f7bb4ea52eecf5a125c75f143955a8e57e1ea17d2d8a48830e7d67db", "transactionIndex": "0x71", "logIndex": "0x13b", "removed": false }, { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000180cd4303582d348a54fcb0436d5665f082a23d1", "0x000000000000000000000000a9d1e08c7793af67e9d92fe308d5697fb81d3e43" ], "data": "0x00000000000000000000000000000000000000000000000000000000aad0a340", "blockHash": "0x90c06a9fad66de217ce5862a92a2d45a8f42f9abf8ec17d5bfb85ffdc7836ca4", "blockNumber": "0x112a883", "blockTimestamp": "0x64ea26b3", "transactionHash": "0x05c9c59145387faa91370e56d717d42856bd20f8f19306814d5b7afbe837efe3", "transactionIndex": "0x66", "logIndex": "0xde", "removed": false }, { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000001c727a55ea3c11b0ab7d3a361fe0f3c47ce6de5d", "0x000000000000000000000000c3dcc088aef4373e665b00fc8be45a4b6a4b24a6" ], "data": "0x000000000000000000000000000000000000000000000000000000000686bcda", "blockHash": "0x90c06a9fad66de217ce5862a92a2d45a8f42f9abf8ec17d5bfb85ffdc7836ca4", "blockNumber": "0x112a883", "blockTimestamp": "0x64ea26b3", "transactionHash": "0xeb8505dd3dc2964c1b43ed40371fb9c907b751ccb9294b99db032270889edc42", "transactionIndex": "0x72", "logIndex": "0xe6", "removed": false }, { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000000000000000000000000000000000000000000000", "0x00000000000000000000000043954785980c50768373dc1dafd7ba3a2052036f" ], "data": "0x000000000000000000000000000000000000000000000000000000e8d433aa6e", "blockHash": "0x7f7889c3686d7f560dd690aeb2a10b49e3a76844543cf78cc0dfe4687e2985d2", "blockNumber": "0x112a884", "blockTimestamp": "0x64ea26bf", "transactionHash": "0xb2c1d8c8e7989adab51dafdc9be24fd05243342b8d08828c4325662de629c690", "transactionIndex": "0x48", "logIndex": "0xbf", "removed": false }, { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000c880d8be4e94f644f1daa7d3558b50ff5a1c2db7", "0x000000000000000000000000ae2d4617c862309a3d75a0ffb358c7a5009c673f" ], "data": "0x0000000000000000000000000000000000000000000000000000002e90edd000", "blockHash": "0x127c6b9794874d7011770b3aa2a0a640e1db201c2d677d01b2b3b19e7ad427c7", "blockNumber": "0x112a885", "blockTimestamp": "0x64ea26cb", "transactionHash": "0x1691b8e89e13f483c549d9b61efbc54ecc54015b8186726911c8458f0993e13d", "transactionIndex": "0x1e", "logIndex": "0x40", "removed": false }, { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000b86fc5e985facdcae6034beefc6cbdcd8bbd8cb4", "0x00000000000000000000000027f713adefc708469d826ab8f5b1e4672f1a070e" ], "data": "0x0000000000000000000000000000000000000000000000000000000059682f00", "blockHash": "0x127c6b9794874d7011770b3aa2a0a640e1db201c2d677d01b2b3b19e7ad427c7", "blockNumber": "0x112a885", "blockTimestamp": "0x64ea26cb", "transactionHash": "0x440106b494b7eb657d19e5f82a164ce4cc62e5252058fab5fb4ee6b92600543c", "transactionIndex": "0x6d", "logIndex": "0xcb", "removed": false }, { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000b4eae88d1643e172518dde0ef213c20628798c65", "0x00000000000000000000000038c5a4f22e2e290ba9bbb9e3d9aaf8582f79f4d1" ], "data": "0x0000000000000000000000000000000000000000000000000000000006b78840", "blockHash": "0xdb63e26b655ad31d60b2173c02127180ed46b28506f785a2682e058ca61afdc1", "blockNumber": "0x112a887", "blockTimestamp": "0x64ea26e3", "transactionHash": "0x21d8e836f79606db39fcd51f7e563ea9c12cfb7a2bb3e36f23f4eb89b93045ea", "transactionIndex": "0x1f", "logIndex": "0x5e", "removed": false }, { "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000579752cff8fee7af09446b2133ee2f9ff10c4fbf", "0x0000000000000000000000001715a3e4a142d8b698131108995174f37aeba10d" ], "data": "0x000000000000000000000000000000000000000000000000000000001d0221cd", "blockHash": "0xdb63e26b655ad31d60b2173c02127180ed46b28506f785a2682e058ca61afdc1", "blockNumber": "0x112a887", "blockTimestamp": "0x64ea26e3", "transactionHash": "0xd78a180e669534a650d842f13d032610e29bf4a7731fb90a6dff73d752ac049c", "transactionIndex": "0x57", "logIndex": "0xb3", "removed": false } ]}