hyperliquid.build
UTC
WEBSOCKET-15-USER-CAP(7)hyperliquid.build HandbookWEBSOCKET-15-USER-CAP(7)

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.

published 2026-09-30updated 2026-09-30

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 error channel: 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.

  • clearinghouseState and openOrders subscriptions push a full state roughly every 5 seconds, wrapped as {dex, user, clearinghouseState}. The shape matches the REST response.
  • userFills sends an isSnapshot: true message first, then individual fills.
  • In our testing during September 2026 the webData2 subscription 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

  1. Read the error channel and the subscriptionResponse messages; fail loudly on the 15-user error.
  2. Count slots across every process on the IP, including test instances.
  3. Prefer 5-second REST polling for anything that does not need sub-second updates.
  4. Budget request weight: userFills and frontendOpenOrders for a busy wallet are large responses and count heavily toward the per-IP rate limit.

See also

hyperliquid.build2026-09-30WEBSOCKET-15-USER-CAP(7)