Building Real-Time Ethereum Event Notifications with BLAZED.sh
When building applications that need to respond to blockchain events in real-time, the choice of infrastructure can make or break your application’s performance and cost efficiency. This is particularly true for applications that use event filters returning large datasets - liquidation monitors, DEX aggregators, or cross-chain bridges that track thousands of events per block.
In this tutorial, we’ll create a webhook service that monitors specific Ethereum events and sends instant notifications to configured URLs. Whether you’re tracking token transfers, monitoring DeFi protocols, or building trading bots, BLAZED.sh provides the low-latency infrastructure and cost-effective pricing needed for production applications.
Understanding BLAZED.sh’s Architecture
BLAZED.sh is optimized for Ethereum applications that require consistent low latency and predictable costs. While many providers offer WebSocket connections, BLAZED.sh provides exclusive IPC socket access (at /tmp/sockets/rpc_proxy.sock
), eliminating network overhead entirely. This architectural choice ensures the lowest possible latency between your application and the Ethereum node.
The real advantage becomes apparent when working with complex event filters that return large responses. A liquidation monitoring service might track hundreds of positions across multiple protocols, or a DEX aggregator might monitor every swap across dozens of pools. These use cases generate massive amounts of data that would quickly become expensive on usage-based pricing models. BLAZED.sh’s flat-rate pricing and IPC-based infrastructure make these applications economically viable.
Event Signatures and Topics: The Building Blocks of Ethereum Events
Before diving into the code, it’s important to understand how Ethereum events work under the hood. Every smart contract event is identified by its signature, which is a 32-byte hash that uniquely identifies the event type. When a contract emits an event, this signature becomes the first topic in the event log.
For example, the standard ERC-20 Transfer event has the signature Transfer(address,address,uint256)
. When hashed using Keccak-256, this becomes 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
. This hash is what we use to filter for specific events across the entire Ethereum blockchain.
Topics in Ethereum events serve as indexed parameters that allow for efficient filtering. The first topic is always the event signature, while topics 2-4 can contain indexed event parameters. For instance, in a Transfer event, topic 2 might be the sender address and topic 3 the recipient address. This indexing system allows us to create precise filters that only capture the events we care about, rather than processing every transaction on the network.
Setting Up the Project
Let’s start by creating a simple webhook service that demonstrates these concepts in action. We’ll focus on the core functionality without unnecessary complexity.
mkdir eth-webhook-service
cd eth-webhook-service
npm init -y
npm install ethers
echo '{"type":"module"}' >> package.json
The Core Webhook Service
Our webhook service will read configuration from a JSON file and establish event listeners for each configured webhook. Here’s the main implementation:
// webhook-service.js
import { ethers } from 'ethers';
import fs from 'fs';
class EthereumWebhookService {
constructor() {
this.provider = null;
this.webhooks = [];
this.subscriptions = new Map();
this.loadConfiguration();
this.connectToEthereum();
}
loadConfiguration() {
const configData = fs.readFileSync('./config.json', 'utf8');
const config = JSON.parse(configData);
this.webhooks = config.webhooks.filter(webhook => webhook.active);
console.log(`Loaded ${this.webhooks.length} active webhooks`);
}
async connectToEthereum() {
const ipcPath = process.env.ETHEREUM_IPC_PATH || '/tmp/sockets/rpc_proxy.sock';
this.provider = new ethers.providers.IpcProvider(ipcPath);
const network = await this.provider.getNetwork();
console.log(`Connected to ${network.name} (Chain ID: ${network.chainId})`);
this.startEventMonitoring();
}
startEventMonitoring() {
this.webhooks.forEach(webhook => {
const filter = { address: webhook.contractAddress, topics: [webhook.eventSignature] };
const eventListener = (log) => this.processEvent(webhook, log);
this.provider.on(filter, eventListener);
this.subscriptions.set(webhook.id, { filter, listener: eventListener });
console.log(`Monitoring ${webhook.name} events`);
});
}
async processEvent(webhook, log) {
const eventData = {
webhook: { id: webhook.id, name: webhook.name },
transaction: { hash: log.transactionHash, blockNumber: log.blockNumber, blockHash: log.blockHash, logIndex: log.logIndex },
event: { address: log.address, data: log.data, topics: log.topics },
timestamp: new Date().toISOString()
};
await this.deliverWebhook(webhook, eventData);
}
async deliverWebhook(webhook, eventData, attempt = 1) {
const maxAttempts = 3;
const response = await fetch(webhook.url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(eventData)
});
if (!response.ok && attempt < maxAttempts) {
setTimeout(() => this.deliverWebhook(webhook, eventData, attempt + 1), Math.pow(2, attempt - 1) * 1000);
}
}
start() {
console.log('Webhook service started');
process.on('SIGINT', () => {
this.subscriptions.forEach((subscription) => {
this.provider.off(subscription.filter, subscription.listener);
});
process.exit(0);
});
}
}
// Start the service
const service = new EthereumWebhookService();
service.start();
Configuration: Defining What Events to Monitor
The power of this webhook service lies in its configuration flexibility. Create a config.json
file that defines exactly which events you want to monitor and where to send notifications:
{
"webhooks": [
{
"id": "usdc-transfers",
"name": "USDC Transfer Monitor",
"url": "https://your-app.com/api/webhooks/usdc-transfers",
"contractAddress": "0xA0b86a33E6441A8BBa8bf0E1b21B6c5D89c9F8F4",
"eventSignature": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"active": true
},
{
"id": "compound-liquidations",
"name": "Compound Liquidation Events",
"url": "https://your-app.com/api/webhooks/liquidations",
"contractAddress": "0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B",
"eventSignature": "0x298637f684da70674f26509b10f07ec2fbc77a335ab1e7d6215a4b2484d8bb52",
"active": true
}
]
}
Each webhook configuration specifies the contract address to monitor and the event signature to filter for. The event signature is crucial - it’s the Keccak-256 hash of the event’s function signature. For the standard ERC-20 Transfer event, this signature Transfer(address,address,uint256)
hashes to the value shown above.
When monitoring DeFi protocols like Compound, liquidation events have their own unique signatures. The beauty of this approach is that you can monitor any event from any contract simply by knowing its signature and contract address.
Understanding Event Data Structure
When an event occurs, the webhook service receives a log object from ethers.js containing several important fields. The data
field contains non-indexed event parameters encoded as hex, while the topics
array contains indexed parameters. The first topic is always the event signature, and subsequent topics contain indexed parameters like addresses or token IDs.
For example, when monitoring USDC transfers, the log structure might look like this: the first topic contains the Transfer event signature, the second topic contains the sender address (padded to 32 bytes), the third topic contains the recipient address, and the data
field contains the transfer amount. Understanding this structure allows you to decode and process events appropriately in your webhook handlers.
Deploying to BLAZED.sh
To deploy your webhook service, build and push your container image to a registry (Docker Hub, GitHub Container Registry, etc.):
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY webhook-service.js ./
CMD ["node", "webhook-service.js"]
Build and push your image:
docker build -t yourusername/eth-webhook:latest .
docker push yourusername/eth-webhook:latest
Then deploy the container on BLAZED.sh with the socket mounted at /tmp/sockets/rpc_proxy.sock
. The platform provides low-latency access to fully synchronized Ethereum nodes with predictable flat-rate pricing.
Testing Your Webhook Service
To test your webhook service locally, you can create a simple HTTP server that logs incoming webhooks:
// test-receiver.js
import http from 'http';
const server = http.createServer((req, res) => {
if (req.method === 'POST') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
const webhook = JSON.parse(body);
console.log('Received webhook:', webhook.webhook.name, webhook.transaction.hash);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ received: true }));
});
}
});
server.listen(8080, () => console.log('Test receiver on port 8080'));
Update your config.json to point to http://localhost:8080
and run both services to see events flowing in real-time.
Advanced Event Filtering and Multiple Topics
While our basic example monitors all events of a specific type from a contract, you can create more sophisticated filters using multiple topics. For instance, if you only wanted to monitor USDC transfers to a specific address, you could modify the event listener setup:
const filter = {
address: webhook.contractAddress,
topics: [webhook.eventSignature, null, ethers.utils.hexZeroPad('0x742d35Cc6634C0532925a3b8D12C0b5cb5aaa1', 32)]
};
This level of precision ensures your webhook service only processes the events that matter to your application, reducing noise and improving performance.
Why BLAZED.sh Makes Economic Sense
The true cost of running Ethereum event monitoring services often isn’t apparent until you’re processing production volumes. Consider a DEX aggregator monitoring all Uniswap V3 pools - during volatile periods, this could mean processing thousands of swap events per block. On traditional usage-based infrastructure, costs can spiral out of control.
BLAZED.sh’s flat-rate pricing model removes this uncertainty. Whether you’re processing 100 events or 100,000 events per day, your costs remain predictable. Combined with the low-latency connection that ensures you never miss time-sensitive opportunities, this makes BLAZED.sh particularly suited for:
- High-frequency trading bots that monitor multiple DEX pools
- Liquidation services tracking thousands of positions across lending protocols
- NFT sniping tools monitoring multiple collections for rare traits
- Cross-chain bridges processing high volumes of transfer events
The infrastructure is optimized to handle event filters that return large datasets efficiently. While WebSocket connections from other providers can work for basic use cases, they often struggle or become prohibitively expensive when dealing with the data volumes required by professional trading operations.
Conclusion
Building real-time Ethereum applications at scale requires infrastructure that can handle high data volumes without breaking the bank. The webhook service we’ve built demonstrates how BLAZED.sh’s low-latency connections and flat-rate pricing model create an economically viable foundation for data-intensive blockchain applications.
Whether you’re building DeFi monitoring tools, NFT marketplace notifications, or custom analytics dashboards, the combination of consistent low latency and predictable costs makes it possible to build applications that would be prohibitively expensive on traditional infrastructure.
Ready to build your own Ethereum webhook service? Deploy on BLAZED.sh and take advantage of infrastructure optimized for high-volume event processing at a fraction of the cost.