Building a Hyperliquid Event Indexer in Go
while working with a startup which had hyperliquid integration, we needed to track order fills and liquidation events for every wallet created on our platform
while working with a startup which had hyperliquid integration, we needed to track order fills and liquidation events for every wallet created on our platform.
Hyperliquid already exposes WebSocket subscriptions for fills and liquidations, but there was one problem: they’re designed around subscribing to individual addresses.
That becomes a bottleneck pretty quickly.
The Problem
Hyperliquid limits the number of active address subscriptions. In our case, we could only subscribe to around 100 wallet addresses for liquidation events.
That works if you’re building a dashboard for a handful of traders.
It doesn’t work when you’re responsible for tracking thousands of user wallets.
We needed something that would scale without worrying about subscription limits.
Looking Under the Hood
Instead of consuming the public WebSocket API, I started looking at how a Hyperliquid validator processes events internally.
Running a Hyperliquid node locally exposes a node_fills directory containing every fill processed by the node. These files contain all order fills, liquidations, and execution details before they’re exposed through the public APIs.
That meant we didn’t need thousands of WebSocket subscriptions anymore.
We just needed to process the source of truth.
The Approach
The indexer continuously tails the node_fills directory, similar to reading an append-only log.
For every fill:
Parse the event.
Check whether either side of the trade belongs to a wallet we’re tracking.
Classify the event (order fill, liquidation, TP/SL).
Broadcast it over WebSockets and publish it to Kafka for downstream consumers.
Our tracked wallets live in Postgres and are refreshed periodically, so adding or removing wallets doesn’t require restarting the service.
Since we’re filtering locally, it doesn’t matter whether we’re tracking 100 wallets or 100,000. Every fill is already available, we simply decide which ones are relevant.
Why Go?
This service spends most of its time doing streaming I/O, parsing JSON, and pushing events.
Go ended up being a natural fit:
lightweight goroutines for concurrent processing
predictable memory usage
fast startup and simple deployment
easy to package alongside the Hyperliquid node
The processor maintains its read offset, so after a restart it resumes exactly where it left off instead of replaying the entire dataset.
Result
Instead of being constrained by subscription limits, we now consume the complete event stream directly from the node and filter it ourselves.
The architecture ended up being much simpler:
Hyperliquid Node
│
node_fills
│
Streaming Indexer
│
Address Filter
│
┌───────────────┬───────────────┐
│ │ │
Kafka WebSocket Internal Services
The service has been running reliably while tracking all platform wallets without worrying about WebSocket subscription caps

