Skip to content

WebSocket subscriptions

Subscribe to new heads and logs over the GraphOps RPC WebSocket endpoint.

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

  1. Install Node.js.

  2. Install tsx and ws.

    npm install tsx ws
  3. Set 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

  1. Install Node.js.

  2. Install tsx and viem.

    npm install tsx viem
  3. Set 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

SubscriptionUse it for
newHeadsNew block headers.
logsLogs matching an address and topic filter.
newPendingTransactionsPending transaction hashes when available for the network.
syncingNode sync status updates.

Recover after a disconnect

Blocks N+2 and N+3 were produced while the socket was down, and a new subscription only delivers events from N+4 onward. One bounded eth_getLogs request fills the gap; deduplicate by log identity where the ranges meet.
  1. Store the last processed block and log identity as you handle each notification.
  2. Reconnect with exponential backoff after a disconnect.
  3. Resubscribe with the same filter.
  4. Backfill with eth_getLogs from the block after the last one you processed up to the first block the new subscription delivers.
  5. Deduplicate by (blockHash, transactionHash, logIndex) where the backfill and the live stream overlap.
  6. Treat logs with removed: true as reversals. See Retries and reorgs.

The backfill is an ordinary HTTPS request. This one reads a single block of USDC Transfer logs:

Prerequisites

  1. Set GRAPHOPS_API_KEY to your GraphOps API key.

# 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    }  ]}

Next steps