Hyperliquid WebSocket API
Stream trades, order books and account events instead of polling, with a 25-line script we ran against mainnet.
Hyperliquid's WebSocket lives at wss://api.hyperliquid.xyz/ws and needs no key to read. Send {"method":"subscribe","subscription":{"type":"trades","coin":"BTC"}} and trades stream in as they print. Each IP may hold 10 connections and 1,000 subscriptions, with at most 10 different users across user feeds. The server closes a connection that has had no message for 60 seconds, so send {"method":"ping"} on quiet feeds.
Polling the info endpoint works, but it burns rate-limit weight and you only ever see snapshots. The WebSocket pushes changes as they happen. It is also the only sensible way to watch more than a handful of markets or accounts at once: Hyperliquid’s own docs say “use websockets for lowest latency realtime data” (rate limits).
Connect and subscribe
| Network | URL |
|---|---|
| Mainnet | wss://api.hyperliquid.xyz/ws |
| Testnet | wss://api.hyperliquid-testnet.xyz/ws |
Every subscription is one JSON message:
{ "method": "subscribe",
"subscription": { "type": "trades", "coin": "BTC" } }
The server confirms it with a subscriptionResponse echoing what you asked for, then sends data messages whose channel names the feed:
{"channel":"subscriptionResponse","data":{"method":"subscribe",
"subscription":{"type":"trades","coin":"BTC"}}}
{"channel":"trades","data":[{"coin":"BTC","side":"A",
"px":"80092.0","sz":"0.24966","time":1793825109482,
"hash":"0x5c99…14cf","tid":304132077357146,
"users":["0xa876…eb29","0x298c…5f5a"]}, …]}
That is a real message from mainnet, 4 November 2026, 20:45 UTC. side is A (ask, a sell hit the bid) or B (bid, a buy lifted the ask). users gives the buyer and seller addresses. Unsubscribe by sending the same subscription with "method": "unsubscribe" (subscriptions).
A working Node.js example
Node 22 and later ship a built-in WebSocket, so this needs no packages. It subscribes to three feeds (BTC trades, the ETH book, and the fills of a public HLP sub-vault), prints the first two messages of each, then exits after 12 seconds. We ran it on 4 November 2026.
// node ws-test.mjs (Node 22+)
const ws = new WebSocket('wss://api.hyperliquid.xyz/ws');
const USER = '0x010461c14e146ac35fe42271bdc1134ee31c703a';
const seen = {};
ws.onopen = () => {
for (const s of [
{ type: 'trades', coin: 'BTC' },
{ type: 'l2Book', coin: 'ETH' },
{ type: 'userFills', user: USER },
]) ws.send(JSON.stringify({ method: 'subscribe', subscription: s }));
// keep quiet connections alive (server drops after 60s)
setInterval(() => ws.send('{"method":"ping"}'), 50_000);
};
ws.onmessage = (e) => {
const m = JSON.parse(e.data);
seen[m.channel] = (seen[m.channel] ?? 0) + 1;
if (seen[m.channel] <= 2) console.log(JSON.stringify(m).slice(0, 300));
};
setTimeout(() => { console.log(seen); process.exit(0); }, 12_000);
On our first run the last line was this (the counts change with market activity):
{ subscriptionResponse: 3, trades: 19, l2Book: 4, userFills: 12 }
The same in Python
Tested with the websockets package (version 16.0). It reads three HYPE trade messages, then checks the heartbeat.
# pip install websockets
import asyncio, json, websockets
async def main():
async with websockets.connect("wss://api.hyperliquid.xyz/ws") as ws:
await ws.send(json.dumps({"method": "subscribe",
"subscription": {"type": "trades", "coin": "HYPE"}}))
for _ in range(3):
msg = json.loads(await ws.recv())
print(msg["channel"], json.dumps(msg["data"])[:120])
await ws.send(json.dumps({"method": "ping"}))
while json.loads(await ws.recv())["channel"] != "pong":
pass
print("pong")
asyncio.run(main())
subscriptionResponse {"method": "subscribe", "subscription": …}
trades [{"coin": "HYPE", "side": "A", "px": "83.511", "sz": "3.03", …
trades [{"coin": "HYPE", "side": "B", "px": "83.511", "sz": "1.79", …
pong
The official Python SDK has a fuller client, websocket_manager.py, with reconnects and a ping thread. Use it if you’re already on the SDK.
The feeds you’ll use most
type | Parameters | What arrives |
|---|---|---|
trades | coin | Every trade: price, size, side, buyer and seller |
l2Book | coin, optional nSigFigs, mantissa, fast | Book snapshots, 20 levels a side (5 if fast) |
bbo | coin | Best bid and offer, only when it changes on a block |
allMids | optional dex | Every mid price; core dex by default |
candle | coin, interval | The live candle, 1m to 1M |
activeAssetCtx | coin | Mark, mid, oracle price, funding, open interest, 24h volume |
allDexsAssetCtxs | none | Contexts for every market on every perp dex |
userFills | user, optional aggregateByTime | A snapshot of recent fills, then each new fill |
orderUpdates | user | Order placed, filled, cancelled |
userEvents | user | Fills, funding, liquidations, non-user cancels; channel is user |
clearinghouseState | user, optional dex | Positions and margin for one perp dex |
userFundings | user | Hourly funding payments |
notification | user | Text notification messages for the account |
There are 24 subscription types in total, including webData3 (what the official front end uses), TWAP feeds, spotState and outcomeMetaUpdates for HIP-4 (full list). For spot markets, use the pair’s @index as the coin, e.g. @107 for HYPE/USDC.
Snapshots on subscribe
Feeds that are a time series (fills, fundings, ledger updates) start with a snapshot of recent history, tagged "isSnapshot": true. Our userFills test got one snapshot message, then live fills. If you persist fills, de-duplicate them: Hyperliquid’s docs say the globally unique trade key is (block time, coin, tid).
User feeds take the account address
Subscribe with the master account or sub-account address you want to watch. An API wallet’s address returns nothing, because API wallets hold no funds; they only sign (docs).
Limits per IP address
From Hyperliquid’s rate limits page:
| Limit | Value |
|---|---|
| Open WebSocket connections | 10 |
| New connections per minute | 30 |
| Subscriptions | 1,000 |
| Unique users across user-specific subscriptions | 10 |
| Messages you send per minute (all connections) | 2,000 |
| Simultaneous in-flight post requests | 100 |
The 10-user cap matters most for trackers. A whale tracker watching 50 wallets can’t subscribe to all of them from one IP. It has to poll clearinghouseState (weight 2 each) for the rest, or use a third-party data provider.
Keep the connection alive
- Heartbeat. The server closes any connection it hasn’t sent a message on for 60 seconds. Send
{"method":"ping"}and the server answers{"channel":"pong"}(heartbeats). A busytradesfeed never goes quiet, but a user feed on an idle account does. - Reconnect. “Disconnection from API servers may happen periodically and without announcement” (WebSocket). On close, reconnect with backoff and send every subscription again. The new snapshot covers what you missed. For anything else, fill the gap with the matching info request, e.g.
userFillsByTime. - Stay under 30 new connections a minute. A reconnect loop without backoff hits that limit within seconds.
Sending requests over the socket
You can also send info requests and signed actions over the same connection, which saves an HTTPS round trip:
{ "method": "post", "id": 1,
"request": { "type": "info",
"payload": { "type": "l2Book", "coin": "BTC" } } }
The reply comes on the post channel with your id. Use a different id for every request so you can match the replies. For "type": "action", the payload is exactly the signed body you would POST to /exchange, so use an SDK to build it. explorer requests are not supported over WebSocket (post requests).
Trading on Hyperliquid is leveraged, and a socket that silently stops updating can leave a bot acting on stale prices. Check the time on every book message and stop trading when it falls behind. Hyperliquid’s terms restrict the United States, Ontario and other places.
This socket covers HyperCore, the exchange. For HyperEVM smart contracts, see HyperEVM RPC and chain settings: the public RPC there has no WebSocket at all.
Questions people ask
What is the Hyperliquid WebSocket URL?
Mainnet is wss://api.hyperliquid.xyz/ws and testnet is wss://api.hyperliquid-testnet.xyz/ws. Reading public and per-address feeds needs no key.
How many WebSocket connections does Hyperliquid allow?
Per IP address: 10 open connections, 30 new connections per minute, 1,000 subscriptions, 10 unique users across user-specific subscriptions, 2,000 messages sent per minute and 100 in-flight post requests.
Why does my Hyperliquid WebSocket disconnect?
The server closes any connection it hasn't sent a message on for 60 seconds, so quiet feeds need a {"method":"ping"} heartbeat. Hyperliquid also says servers may disconnect without warning, so every client must reconnect and resubscribe.
Can I place orders over the Hyperliquid WebSocket?
Yes. Wrap a signed exchange action in {"method":"post","id":n,"request":{"type":"action","payload":…}}. The signing is the same as for HTTP, so use an SDK. Info requests can be posted the same way with type "info".
Sources
- Hyperliquid Docs — WebSocket (URLs, reconnecting)
- Hyperliquid Docs — WebSocket subscriptions (types and data formats)
- Hyperliquid Docs — WebSocket post requests
- Hyperliquid Docs — Timeouts and heartbeats
- Hyperliquid Docs — Rate limits and user limits (WebSocket limits)
- hyperliquid-python-sdk — websocket_manager.py (reference client)