7Block Labs
Blockchain Development

ByAUJay

Summary: This guide walks you through using Infura’s WebSocket eth_subscribe interface to get real-time Ethereum data. Inside, you’ll find solid examples ready for production, some handy credit budgeting math, and tips for building failure-resilient patterns around newHeads/newBlockHeaders, logs, and pending transactions on both mainnet and testnets. It’s crafted for architects who want the nitty-gritty details to deliver dependable, cost-effective systems at scale. (docs.metamask.io)

Infura WebSocket eth_subscribe Logs Documentation: Subscribe NewBlockHeaders and Beyond

Decision-makers are really starting to expect real-time user experiences. Think about it: balances that refresh the moment a transfer comes through, dashboards that update right when a validator proposes a block, and operational alerts that trigger before users even notice something's up. For Ethereum, this all boils down to using WebSockets and the eth_subscribe method.

Here's a straightforward playbook for getting your Infura subscriptions up and running in production. We’ll cover everything from specific endpoints and pricing calculations to testnet coverage, code that can handle idle timeouts, and event handling that’s safe from reorgs.


What eth_subscribe gives you on Infura in January 2026

Infura has your back with stateful WebSocket subscriptions across various networks. If you're diving into Ethereum, here are the WSS endpoints you’ll want to use:

  • Mainnet: wss://mainnet.infura.io/ws/v3/ (docs.metamask.io)
  • Sepolia testnet: wss://sepolia.infura.io/ws/v3/ (docs.metamask.io)
  • Hoodi testnet: wss://hoodi.infura.io/ws/v3/ (Holesky is out of the picture; Hoodi has taken over for validator testing) (docs.metamask.io)

Supported Ethereum Subscription Types on Infura:

When you're working with Infura, you'll find a few different Ethereum subscription types that you can use to keep track of changes on the network. Here's the rundown:

  1. New Heads:

    • Get notified whenever a new block is added to the blockchain.
    • Perfect for tracking the latest developments in real-time.
  2. Pending Transactions:

    • Subscribe to incoming transactions that are waiting to be mined.
    • Great for developers who need to monitor the flow of transactions.
  3. Logs:

    • Listen for specific events emitted from smart contracts.
    • Useful if you're building dApps and need updates on contract interactions.
  4. Pending Logs:

    • Similar to Logs, but for events tied to transactions that haven't been mined yet.
    • Helps you keep an eye on unconfirmed activity.
  5. Eth Method Calls:

    • Subscribe to method calls on smart contracts, allowing you to see changes in real-time.
    • Ideal for monitoring contract states or specific functions being called.

These subscription types can make your life a lot easier by keeping you in the loop on what's happening with Ethereum. Just pick the ones that suit your needs and get started!

  • newHeads -- This streams new block headers, even when there are reorganizations happening. You can check it out here.
  • logs -- These are the contract events that match a specific address or topics filter. Just so you know, it’ll emit removed: true if a reorg occurs. More details here.
  • newPendingTransactions -- This shows you pending transaction hashes, although it’s not available on every network. For more info, visit this link.

Infura really encourages you to filter logs by address and/or topics. It’s a smart way to manage data volume and keep costs in check. You can check out the details here: (docs.metamask.io).


newHeads vs newBlockHeaders: the naming nuance you must get right

  • To get started, the basic JSON-RPC method you'll want to use is eth_subscribe with the subscription type set to "newHeads." You can check out the details here: (geth.ethereum.org).
  • A lot of client libraries, especially web3.js, offer this as "newBlockHeaders." They essentially provide the same stream of block headers. If you're curious about it, take a look at this link: (support.infura.io).

Quick Smoke Test with wscat:

To kick things off, let’s run a quick smoke test using wscat. This handy tool lets you connect to WebSocket servers and send/receive messages.

Setting Up wscat

If you haven’t installed wscat yet, you can easily get it via npm. Just pop open your terminal and run:

npm install -g wscat

Connecting to Your WebSocket Server

Now that you have wscat, you can connect to your WebSocket server. Replace your-websocket-url with your actual server URL:

wscat -c ws://your-websocket-url

Sending a Test Message

Once you’re connected, you can send a test message. For example, type:

{"type": "ping"}

And hit enter. You should get a response back from your server if everything is working smoothly!

Closing the Connection

When you’re done testing, you can close the connection by typing:

Ctrl + C

Summary

And there you have it--a simple way to smoke-test your WebSocket server using wscat. It’s quick, efficient, and super easy to use. For more info, check out the wscat documentation. Happy testing!

# Subscribe to new block headers (mainnet)
wscat -c wss://mainnet.infura.io/ws/v3/<YOUR-API-KEY> \
  -x '{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newHeads"]}'

You’ll get eth_subscription notifications for every block header that gets announced. Check out the details here: (docs.metamask.io).

Production note: Just a heads up, Infura charges fees based on how many events you subscribe to. For instance, each newHeads block event will set you back 50 credits. With roughly 7,200 blocks happening every day, if you have a newHeads feed that’s always on, you'll be looking at about 360,000 credits each day. Plan your budget with that in mind! (docs.metamask.io)


Logs subscriptions that won’t wake you at 3 a.m.

The logs stream sends out event logs from new blocks that fit your filter. Here are two key things you should definitely keep in mind:

  • Reorgs are pretty clear-cut: the same log can show up twice, with removed: true when the old chain gets orphaned. Your code should handle delivery as at-least-once. (geth.ethereum.org)
  • Subscriptions only look forward; if you need to catch up on historical data, that's where eth_getLogs comes in. Don't bother using fromBlock/toBlock in eth_subscribe. Instead, try using a rolling cursor and backfill when you reconnect (more details below). (geth.ethereum.org)

Example Filters and OR-Patterns

When diving into data analysis, it's super helpful to play around with filters and OR-patterns. Here’s a quick guide on what you can do:

Basic Filters

Filters let you narrow down your data to only what you need. For instance:

  • Date Filters: Want to see data from last month? You can set a filter for that specific range.
  • Value Filters: If you’re interested in transactions over a certain amount, just apply a filter to show only those.
  • Text Filters: Looking for specific keywords? Filter out everything else to focus on what’s relevant.

OR-Patterns

OR-patterns come in handy when you’re searching for multiple criteria at once. Instead of feeling limited, you can expand your search. Here’s how it works:

  • Combining Keywords: If you’re interested in articles that mention either "apple" or "orange," you can set up an OR-pattern to grab both.
  • Multiple Conditions: Say you want to check for sales that either occurred in "January" or "February" - an OR-pattern can help you pull those up without hassle.

Example Code Snippet

Here’s a quick example to demonstrate how you might set up an OR-pattern in code:

query = "SELECT * FROM sales WHERE month = 'January' OR month = 'February'"

Visual Representation

Check out this table that illustrates how filters and OR-patterns can look in practice:

Filter TypeExampleResult
Date FilterLast 30 DaysDisplays data from the past month
Value Filter> $100Shows transactions over $100
Text FilterContains "apple"Lists entries with the keyword "apple"
OR-Pattern"apple" OR "orange"Pulls in all entries with either fruit

With these tools at your disposal, you can easily sift through data and find exactly what you’re searching for. Happy analyzing!

  • An address can either be just one single address or a bunch of them (you know, like for multiple contracts).
  • The topics array lets you use null wildcards and even OR by nesting arrays (for example, you can do [ [topicA, topicB], null, topicC ]). This is super handy for narrowing down your volume without needing to set up a bunch of different subscriptions. (ethereum.org)

wscat Example: Listen to ERC‑20 Transfer by Topic on a Single Contract

To get started with wscat and listen for ERC-20 Transfer events on a specific contract, you'll first want to set up your WebSocket connection. Here’s how you can do it:

  1. Install wscat
    If you haven't installed wscat yet, you can do so using npm:

    npm install -g wscat
  2. Connect to the WebSocket
    Open up your terminal and run the following command, replacing with your actual Infura WebSocket endpoint:

    wscat -c wss://<YOUR_INFURA_ENDPOINT>
  3. Subscribe to the Transfer Events
    Once you're connected, you need to send a subscription message to listen for Transfer events. Use the following JSON-RPC message, making sure to replace with the address of the ERC-20 contract you're interested in:

    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "eth_subscribe",
      "params": [
        "logs",
        {
          "address": "<CONTRACT_ADDRESS>",
          "topics": [
            "0xddf252ad1be2c8b11e8b45d1d50b706e1f744c5cddf6e6f87e9224157f2e18e"  // Transfer event signature
          ]
        }
      ]
    }
  4. Receive Transfer Events
    After sending the subscription message, you should start receiving logs for every Transfer event emitted by the specified contract. Each message will contain details like the from, to, and value for the transfer.

That's it! You're all set to listen for ERC-20 Transfer events on your chosen contract. Keep an eye on your terminal for updates!

# keccak256("Transfer(address,address,uint256)")
export TRANSFER_TOPIC=0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
wscat -c wss://mainnet.infura.io/ws/v3/<YOUR-API-KEY> \
  -x '{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["logs",{"address":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","topics":["'$TRANSFER_TOPIC'"]}]}'

You'll want to look out for fields such as address, topics, data, blockNumber, transactionHash, logIndex, and removed. Make sure to handle the removed field properly so you can roll back the application state whenever you need to. (geth.ethereum.org)

Cost Control

When it comes to logs, events are charged at a rate of 300 credits per block. The cool part? If your filter doesn’t actually match that block, you won’t get hit with the charge for that event. So, if you want to keep your credit usage in check, tightening those filters is the way to go. Check out more details here.


Pending transactions: powerful, noisy, and not on every network

By subscribing to the newPendingTransactions stream, you’ll get a live feed of transaction hashes as they pop into the mempool. Infura bundles up these updates roughly every 700-800 ms and it’ll set you back 200 credits for each aggregated event. Just a heads-up, this feed isn’t supported on all networks, so make sure to check that before you go ahead and launch. (docs.metamask.io)

wscat -c wss://mainnet.infura.io/ws/v3/<YOUR-API-KEY> \
  -x '{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newPendingTransactions"]}'

Use Cases That Benefit from Pending

If you’re dealing with slippage-sensitive operations, pending transactions can really come in handy. Here are a few scenarios where it shines:

  • Mempool Monitoring: Keep an eye on transactions in the mempool for those situations where every millisecond counts.
  • Pre-Confirmation UX Nudges: Give users better insights and prompts before their transactions go through, making the experience smoother.
  • Risk Analytics: Analyze ongoing operations to spot potential risks in real-time.

For the rest of the crowd, it’s usually smarter to stick with newHeads and logs. They offer lower noise and are more cost-effective, making them a solid choice for everyday operations.


Library‑specific, production‑ready snippets

Web3.js (v1.x) with Keepalive and Reconnect

If you're diving into Web3.js (version 1.x), you might be interested in making your application a bit more resilient. One way to do that is by implementing keepalive and reconnect features. Here’s how you can do it.

Setting Up Web3.js

First, make sure you've got Web3.js installed in your project. If you haven’t already, you can add it via npm:

npm install web3

Basic Initialization

Start by creating a Web3 instance. Here’s a simple setup:

const Web3 = require('web3');

// Replace with your WebSocket provider URL
const providerUrl = 'wss://your.websocket.provider';
const web3 = new Web3(new Web3.providers.WebsocketProvider(providerUrl));

Implementing Keepalive

To keep your connection alive, you might want to send periodic "ping" messages. Here's how you can do that:

const pingInterval = 30000; // 30 seconds
const keepAlive = () => {
    setInterval(() => {
        web3.eth.net.isListening()
            .then(() => console.log('Connection is alive!'))
            .catch(error => console.error('Connection lost:', error));
    }, pingInterval);
};

keepAlive();

Reconnect on Disconnection

To reconnect when the WebSocket connection drops, you can listen for the error and close events on the WebSocket provider. Here’s a simple reconnect strategy:

const connect = () => {
    const webSocketProvider = new Web3.providers.WebsocketProvider(providerUrl);

    webSocketProvider.on('error', (error) => {
        console.error('WebSocket error:', error);
        reconnect();
    });

    webSocketProvider.on('end', () => {
        console.warn('Connection closed, attempting to reconnect...');
        reconnect();
    });

    web3.setProvider(webSocketProvider);
};

const reconnect = () => {
    setTimeout(() => {
        console.log('Reconnecting...');
        connect();
    }, 5000); // try to reconnect every 5 seconds
};

connect();

Wrapping It All Up

With this setup, you've got a Web3.js application that can maintain a stable connection thanks to keepalive messages and the ability to automatically reconnect if the connection drops. Just make sure to replace providerUrl with your actual WebSocket provider URL. Happy coding!

const Web3 = require('web3');

const provider = new Web3.providers.WebsocketProvider(
  'wss://mainnet.infura.io/ws/v3/<YOUR-API-KEY>',
  {
    clientConfig: { keepalive: true, keepaliveInterval: 60_000 },
    reconnect: { auto: true, delay: 5_000, maxAttempts: 5, onTimeout: false },
  }
);

const web3 = new Web3(provider);

// Keep the socket warm and resilient
web3.eth.subscribe('newBlockHeaders')
  .on('connected', (id) => console.log('newBlockHeaders connected', id))
  .on('data', (hdr) => console.log('block', Number(hdr.number)))
  .on('error', (e) => console.error('subscription error', e));

Infura will close idle WebSocket connections after about an hour. To keep your connection alive, you can use keepalive or stay subscribed to newBlockHeaders. For more details, check out this link: (support.metamask.io).

Ethers v6 with InfuraWebSocketProvider

If you're diving into Ethereum development and want to leverage the power of Ethers.js along with Infura's WebSocket provider, you’re in the right spot. Here’s a straightforward guide to get you set up.

Getting Started

First, make sure you have Ethers.js installed in your project. If you haven’t done this yet, you can easily add it via npm:

npm install ethers

Setting Up the Infura WebSocket Provider

Next, you'll need to connect to the Infura WebSocket provider. If you don’t have an Infura project yet, head over to Infura and create one. After that, grab your WebSocket endpoint URL from your project dashboard.

Now, you can set up your Infura WebSocket provider like this:

import { ethers } from "ethers";

// Replace with your Infura WebSocket URL
const infuraWsUrl = "wss://mainnet.infura.io/ws/v3/YOUR_INFURA_PROJECT_ID";

const provider = new ethers.providers.WebSocketProvider(infuraWsUrl);

Listening to Events

Once you’re connected, you can start listening for events. For example, let’s say you want to monitor a specific Ethereum address for any incoming transactions:

const addressToWatch = "0xYourEthereumAddressHere";

provider.on("pending", (tx) => {
  provider.getTransaction(tx).then((transaction) => {
    if (transaction && (transaction.to === addressToWatch || transaction.from === addressToWatch)) {
      console.log(`Transaction involving ${addressToWatch}:`, transaction);
    }
  });
});

Closing the Connection

Don’t forget to clean up and disconnect when you’re done. This is super important to avoid any unnecessary resource usage:

provider.destroy();

Conclusion

That’s it! You’re now set up to use Ethers.js with Infura's WebSocket provider. This allows you to stay in the loop with real-time Ethereum events, making it easier to build responsive and interactive dApps. Happy coding!

import { ethers } from 'ethers';

const network = 'mainnet';
const infura = ethers.InfuraProvider.getWebSocketProvider(network, process.env.INFURA_API_KEY);

// 1) newHeads via 'block' events
infura.on('block', (blockNumber) => {
  console.log('new block', blockNumber);
});

// 2) logs filter (ERC-20 Transfer on WETH)
const TRANSFER_TOPIC = ethers.id('Transfer(address,address,uint256)');
const filter = {
  address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
  topics: [TRANSFER_TOPIC],
};
infura.on(filter, (log) => {
  // log.removed => boolean on reorg
  console.log('log', log.blockNumber, log.transactionHash, log.removed);
});

InfuraWebSocketProvider is the way to go in ethers v6! When you're connecting to Infura, it's definitely better to use this specific provider rather than the generic WebSocketProvider. Check out the details in the official docs.

Unsubscribe to Stop Charges and Access Free Resources

If you're looking to avoid unwanted charges and want to take advantage of free resources, here's what you need to do:

  1. Visit Your Account Settings: Head over to your account settings on the website.
  2. Find the Subscription Section: Look for the subscription or billing section--this is usually where you'll manage your payment options.
  3. Unsubscribe: Click on the unsubscribe button or link to cancel your subscription.
  4. Confirm: Follow any prompts to confirm your cancellation.
  5. Explore Free Resources: Once you've unsubscribed, don’t forget to check out the free resources available to you. You can find them in the Resources or Help section of the site.

Taking these steps will help you avoid any unexpected charges while still giving you access to a wealth of free information and tools!

// Web3.js
const sub = web3.eth.subscribe('newBlockHeaders');
await sub.unsubscribe(); // eth_unsubscribe

// ethers v6
infura.off('block');    // removes listeners

Just a heads up, calling eth_unsubscribe will set you back 10 credits, so make sure to use it when you're all done. You can check out more details here.


Finality, reorgs, and correctness

  • Treat subscription deliveries like they’re at least once. When you’re looking at logs, keep an eye out for duplicates and notice that removed: true shows up during reorgs. It’s a good idea to create idempotent handlers that are based on (blockHash, logIndex). Check it out here: (geth.ethereum.org).
  • When you're setting confirmation thresholds for follow-up queries, think about using “safe” and “finalized” block tags. For instance, make sure to check that a log’s block is finalized before you notify users. These tags are part of the standard JSON-RPC block parameters, according to EIP-1898 and the JSON-RPC spec. More details here: (eips.ethereum.org).
  • If you need to backfill after reconnecting, start by caching the last seen block number (or header hash). Then, when you reconnect, follow these steps:

    1. Grab the latest safe or finalized block;
    2. Use eth_getLogs over the range [lastSeen+1 … latestSafe];
    3. Replay to your app’s state;
    4. Get back to your live subscription.
      This process helps you avoid any silent data loss while the socket was offline. For more info, check this out: (support.metamask.io).

Exact credit math and rate limits you can plan around

Infura operates on a credit-based billing model specifically for methods and subscription events:

  • eth_subscribe call: This one will set you back 5 credits, and it’s just a one-time fee for each subscription. (docs.metamask.io)
  • Event charges:
    • newHeads: You’re looking at 50 credits for every block event, which could add up to around 360k per day if you keep it running all the time.
    • logs: This can cost you up to 300 credits for each block that fits your criteria--though it’ll be zero if there’s no match.
    • newPendingTransaction: This will take 200 credits for an aggregated event roughly every 0.7 to 0.8 seconds. (docs.metamask.io)
  • eth_unsubscribe: Unsubscribing will cost you 10 credits. (docs.metamask.io)

Daily and per‑second quotas (as of late 2025/early 2026; make sure to check your plan):

  • Per-second caps: For the Core plan, you get 2,000 credits per second; Developers get 4,000; and Teams have a nice 40,000. If you hit the throughput cap, you’ll see a 429 response, but don’t worry, it won’t cost you anything. (support.infura.io)
  • Daily credits: This one depends on your plan, so check out your dashboard or help center for the latest info on your allowances and what happens when you hit the daily cap reset at 00:00 UTC. (support.infura.io)

Rule of Thumb Budgeting

When it comes to managing your money, a good rule of thumb can really help keep things in check. Instead of getting lost in the details, these simple guidelines make budgeting a breeze.

50/30/20 Rule

One of the most popular methods out there is the 50/30/20 rule. The idea is pretty straightforward:

  • 50% for Needs: This covers all your essentials like rent, groceries, utilities, and any bills you just can't avoid.
  • 30% for Wants: Here’s where you can splash out a bit! This includes your entertainment, dining out, shopping, and other fun stuff.
  • 20% for Savings: This is all about your future. Whether it's for retirement, an emergency fund, or a vacation, putting away a chunk of your income is key.

The 80/20 Rule

Another approach is the 80/20 rule. The beauty of this one is its simplicity.

  • 80% of your income goes towards your spending. This keeps your lifestyle comfortable without going overboard.
  • 20%? That's for savings and investments. It's all about building your wealth over time.

The 70/20/10 Rule

If you're looking for something a bit different, consider the 70/20/10 rule:

  • 70% for your everyday expenses. This is where you handle your needs and wants.
  • 20% for savings--think retirement or an emergency fund.
  • 10% for giving back, whether it’s donations or helping out friends and family. It's a nice way to keep generosity in mind!

Making It Work

Whichever rule you choose, the key is to find a budgeting style that feels right for you. Here are a few tips to help you get started:

  • Track your spending: Use apps or even good old-fashioned spreadsheets to see where your money goes.
  • Adjust as needed: Don’t be afraid to tweak these percentages based on your lifestyle or financial goals.
  • Stay disciplined: Budgeting is all about balance. Stick to your plan, but allow for some flexibility when life throws you curveballs.

Conclusion

Budgeting doesn't have to be boring or complicated. Using these simple rules of thumb can help you take charge of your finances without getting overwhelmed. Remember, it’s all about finding what works best for you!

  • You’ll need a single, always-on newHeads feed per environment, which burns through about ~360k credits a day.
  • For a well-filtered logs feed focused on a busy protocol, you’re looking at a credit usage that can swing from virtually nothing (just a handful of matches) all the way up to millions of credits a day if your filter picks up loads of events. It’s a good idea to start with something specific: try filtering by a single address and an exact event signature for topic0. Check out the details here: docs.metamask.io

Network coverage (and why Hoodi matters)

You can access subscriptions on the Ethereum mainnet and key testnets through the WSS endpoints we mentioned before. Just a heads-up: as of September 2025, Holesky is no longer in the game. The Hoodi testnet is now the go-to for validators, and you can find HTTPS and WSS endpoints for it on Infura. For your application testing, it's best to stick with Sepolia, while Hoodi is the way to go for validator and infrastructure scenarios. Check out more details at (ethereum.org).

Infura has got your back with WebSocket subscriptions on big players in the Layer 2 space, like Arbitrum, Optimism, Base, Polygon, Scroll, and ZKsync Era mainnet. Just a heads up, some of these are still in public beta. Make sure to check the support for each chain, especially when it comes to newPendingTransactions. (docs.metamask.io)


Best emerging practices we see succeeding in 2025-2026

  1. Multiplex minimal subscriptions; filter hard
  • It’s cheaper to maintain fewer, broader filters compared to a bunch of tiny subscriptions, but this only works if those filters are spot on (think address + topic0). Mix it up with topics OR arrays to bring together closely related events. Check it out on (ethereum.org).
  1. Don't forget to set up reconnect, idle keepalive, and backfill!
  • Infura tends to close idle sockets after about an hour, so it's a good idea to set the keepaliveInterval to around 60 seconds. You can keep things warm by holding onto a small newBlockHeaders subscription. When you reconnect, make sure to backfill using eth_getLogs with your block cursor. Check out more details here.

3) Program for Reorgs as a First-Class Case

  • When it comes to reorgs, if you get up to N confirmations, it’s possible that logs might get tossed out. If that happens: set the flag to true, roll back any side effects (like reducing balances or canceling notifications), and just hang tight for the finalization before you do anything that can’t be reversed. (geth.ethereum.org)
  1. Split “user UX” from “accounting truth” paths
  • Leverage newHeads/logs to drive instant UI updates, while also syncing to a durable queue like Kafka. Then, rehydrate with finalized data for those back-office pipelines.
  1. Keep an eye on costs with a per-service budget
  • Turn those product SLOs into credit budgets. For instance, you could limit a microservice to 500k credits per day by focusing on specific topics and hitting pause on subscriptions when traffic is low. The event credits for newHeads/logs/pending are pretty straightforward and are already available for you. Check it out here: (docs.metamask.io)
  1. Detect indexing edge cases (last block race)
  • If you're noticing that eth_getLogs for the tip seems a bit sparse, take a peek at the logsBloom for that block. It’ll help you confirm whether logs are actually there before you try again; experiencing slight delays at the very tip is totally normal. (support.metamask.io)

Concrete patterns you can copy

A) Resilient “subscribe + backfill” loop (pseudocode)

while true do:
    messages = subscribeToQueue()
    for message in messages do:
        processMessage(message)

    if noMessagesReceived() then:
        backfillMessages()
let lastSeen = loadCheckpoint(); // block number you processed to completion

function onConnected() {
  // 1) Backfill safely to the latest safe block
  const latestSafe = eth_getBlockByNumber('safe');        // tag is standard
  const from = lastSeen + 1;
  if (from <= latestSafe.number) {
    const logs = eth_getLogs({ fromBlock: from, toBlock: latestSafe.number, address, topics });
    process(logs); // idempotent handlers; handle duplicates
    lastSeen = latestSafe.number;
    saveCheckpoint(lastSeen);
  }

  // 2) Subscribe for live events
  subscribe('logs', { address, topics }, onLog, onError);
}

function onLog(log) {
  process([log]);
  if (!log.removed) {
    lastSeen = Math.max(lastSeen, log.blockNumber);
    saveCheckpoint(lastSeen);
  }
}

The trick is making sure the transition from historical data to live data is smooth--no gaps or double counting. Plus, having idempotent consumers helps manage any duplicates or removed logs. Check out more about it here.

B) Multi-contract or Filtering in One Subscription

const filter = {
  address: [
    '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
    '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT
  ],
  topics: [ethers.id('Transfer(address,address,uint256)')],
};
// One subscription covers both contracts’ Transfer events.

Stick to one subscription when the event schema is the same and the consumers are identical. It’s way easier and more cost-effective than juggling two separate feeds. (ethereum.org)

C) A Cost-Conscious Planning Example

  • Here's what you need: 1 global newHeads, 1 log for your protocol, and 1 pending (only during business hours).
  • Daily baseline: newHeads is about 7,200 blocks multiplied by 50, which gives you roughly 360k credits.
  • For logs: if your filter catches about 1 in every 60 blocks, that means you’re looking at around 120 matching blocks times 300, totaling about 36k credits.
  • As for pending: if you factor in 8 hours a day, 60 minutes, around 75 events per minute, you're looking at approximately 72,000 credits.
  • All that adds up to around 468k credits per day. This should fit nicely within the Developer plan at 15M/day; even the Core plan with 6M/day should work fine unless you've got heavier usage elsewhere. If you need a little extra wiggle room, try adjusting those filters first. (docs.metamask.io)

Operational guardrails

  • Backpressure: Make sure your subscribers are processing events quickly! If you're dealing with some heavy lifting, it's a good idea to buffer those tasks in a queue and acknowledge off the WebSocket as soon as you can.
  • Error semantics:
    • 429 means you've hit the throughput limit (don’t worry, you won’t get charged for this).
    • 402 signals that you’ve exceeded your daily credits (again, no charges here); you can pick up right where you left off after the daily reset. So, definitely set up some alerts and fallback strategies! Check out more details in the MetaMask docs.
  • Security: Keep your API keys safe on the server side! If you really need to use them client-side, you should run them through your backend and multiplex those subscriptions to keep things secure.
  • Lifecycle: Always remember to call eth_unsubscribe or remove listeners when a user navigates away. This will help you avoid racking up costs for feeds that aren’t being used. You can find more info in the MetaMask docs.

Troubleshooting checklist (fast wins)

  • “My WebSocket closed overnight.”
    Make sure to add keepalive/reconnect and keep a newBlockHeaders subscription since Infura tends to close idle sockets after about an hour. Check out this guide for more details.
  • “I missed events during a disconnect.”
    You can use your checkpoint along with eth_getLogs to fill in those gaps and get back to live updates. Here’s a helpful link for that: here.
  • “I saw a log, then removed: true.”
    That’s a reorg. You’ll want to revert any state that came from that log and hang tight until you get a safe/finalized confirmation before making any irreversible moves. More info can be found here.
  • “Which endpoint do I use for testnets now?”
    For app testing, go with Ethereum’s Sepolia; and for validator scenarios, you should use Hoodi. Just make sure to stick to the specific WSS endpoints that Infura has published. You can find the details here.
  • “Do I need multiple sockets?”
    You can start off with one socket per service and multiplex your subscriptions. Only split them up if you really need to, like when you're facing throughput issues or specific failure domains. And don’t forget to check if the network supports newPendingTransactions! More information is available here.

Copy‑paste quickstart for your runbook

Raw JSON‑RPC Messages

When you're working with JSON-RPC, you'll often come across raw messages that look something like this:

{
  "jsonrpc": "2.0",
  "method": "subtract",
  "params": [42, 23],
  "id": 1
}

This is a basic example where we're calling the subtract method with two parameters: 42 and 23. The id field is there to keep track of the request, and the jsonrpc field indicates the version.

Here's a breakdown of each part:

  • jsonrpc: This tells you that we're using version 2.0 of the JSON-RPC protocol.
  • method: The actual function we're trying to call.
  • params: This is where we pass any inputs needed for that method.
  • id: A unique identifier for the request, which helps in matching the response back to the request.

When you get a response, it might look something like this:

{
  "jsonrpc": "2.0",
  "result": 19,
  "id": 1
}

In this case, the result field contains the output from our subtract method, showing that 42 - 23 equals 19. The id matches the request, making it easy to connect the two.

Why Use JSON-RPC?

Opting for JSON-RPC can simplify interactions between a client and server. Here are a few reasons why it might be a good fit for your project:

  • Lightweight: Since JSON is easy to read and write, JSON-RPC is less complex than many other protocols.
  • Language Agnostic: You can use JSON-RPC with any programming language that can handle JSON.
  • Easy Error Handling: The protocol includes built-in error messages, so it’s clear when something goes wrong.

Summary

Raw JSON-RPC messages are straightforward. They help you request and receive data between systems effectively. If you're venturing into remote procedure calls, understanding these messages will definitely come in handy!

// Subscribe new heads
{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newHeads"]}

// Subscribe logs with address + topic0 filter
{"jsonrpc":"2.0","id":2,"method":"eth_subscribe",
 "params":["logs",{"address":"0xYourContract","topics":["0xTopicHash"]}]}

// Unsubscribe
{"jsonrpc":"2.0","id":3,"method":"eth_unsubscribe","params":["0x<subscription-id>"]}

Reference Behaviors

  • Removed on Reorgs: Keep in mind that certain behaviors get taken out during reorganizations.
  • Logs Filter: The logs filter is pretty flexible--it supports OR arrays and even null wildcards, making it easier to query what you need.
  • Subscriptions: Just a heads-up, subscriptions are only available via WebSocket. You won’t be able to use them through other means. You can find more details on this here.

The bottom line for decision‑makers

  • Subscriptions are a great way to achieve low-latency user experiences and keep an eye on your operations--just make sure to budget for credits and design for reorganizations.
  • With Infura’s transparent pricing (you pay per event), you can easily keep your costs in check. Plus, you can manage your spending with filters and schedules. Check it out here!
  • Say goodbye to Holesky--Hoodi is taking its place in testnet planning! Be sure to update your environments and CI pipelines to reflect this change. Learn more here.

If you're looking for a hassle-free setup that’s safe for replay, comes with cost limits, and includes dashboards and alerts, 7Block Labs has got you covered. They deliver robust subscription services that can be integrated into your system in just a few days, not weeks.


Sources and further reading

  • If you're working with Infura or MetaMask, check out eth_subscribe, networks, WebSockets, and their pricing details. You can find more about it here.
  • Want to dive into Geth's RPC pub/sub mechanics? Just a heads up, logs get wiped out during reorganization (reorg). More info is available here.
  • Curious about JSON‑RPC block tags and EIP‑1898? This will help you understand finalized and safe usage. Check it out on ethereum.org.
  • Here’s a handy tip: keep your WebSocket subscriptions alive and make sure to reconnect if things go south. You can learn how to do this here.

Like what you're reading? Let's build together.

Get a free 30-minute consultation with our engineering team.

7BlockLabs

Full-stack blockchain product studio: DeFi, dApps, audits, integrations.

7Block Labs is a trading name of JAYANTH TECHNOLOGIES LIMITED.

Registered in England and Wales (Company No. 16589283).

Registered Office address: Office 13536, 182-184 High Street North, East Ham, London, E6 2JA.

© 2026 7BlockLabs. All rights reserved.