Name
The WebSocket cap: 15 tracked users per IP, and the error that never disconnects you
Address-based WebSocket subscriptions on Hyperliquid stop at 15 distinct users per IP. The API tells you once, on the error channel, and keeps the socket open. Here is how we found it and what we do instead.
We built a wallet-tracking feature that subscribes to userFills, userEvents, clearinghouseState and openOrders for a list of addresses. The log said "subscribed to 89 addresses". Ten of them produced data. This note explains why, and how to design around it.
The behaviour we observed (September 2026)
- Address-based subscriptions are capped at 15 distinct users per source IP, counted across all connections and all processes from that IP.
- When you exceed the cap, the server sends one message on the
errorchannel:Cannot track more than 15 total users.The socket stays open. Every other subscription keeps streaming. Nothing is retried. - Slots are released with a delay after a connection closes, so a restart can briefly behave as if the cap were lower. This shows up as "works sometimes".
- A local test instance on the same machine consumes slots from the same pool as production.
If your client does not read the error channel, a subscription beyond the cap is silent forever. "Subscribed to N addresses" in your own log proves nothing until you have seen a subscriptionResponse for each one.
Related quirks worth knowing
clearinghouseStateandopenOrderssubscriptions push a full state roughly every 5 seconds, wrapped as{dex, user, clearinghouseState}. The shape matches the REST response.userFillssends anisSnapshot: truemessage first, then individual fills.- In our testing during September 2026 the
webData2subscription failed with a JSON parse error and could not be used. Verify against the current docs before depending on it.
What we do instead
For a single wallet, polling REST clearinghouseState every 5 seconds gives the same update rate as the subscription, uses no slot, and is easy to back off. We animate PnL between polls by marking positions to the live mid and anchoring to the exchange's own unrealised PnL when it arrives.
For many wallets we split the list: up to 15 live subscriptions for the addresses that matter most, and a REST poll for the rest. A design that needs more than 15 live users per IP has to spread across IPs; there is no client-side workaround.
Checklist
- Read the
errorchannel and thesubscriptionResponsemessages; fail loudly on the 15-user error. - Count slots across every process on the IP, including test instances.
- Prefer 5-second REST polling for anything that does not need sub-second updates.
- Budget request weight:
userFillsandfrontendOpenOrdersfor a busy wallet are large responses and count heavily toward the per-IP rate limit.