Skip to main content

WebSocket

Environment​

EnvironmentBase URL
Productionwss://api-studio.r25.xyz

1. Document Overview & Capabilities​

This document is for external partners integrating the OpenAPI trading WebSocket interface. It describes the trading endpoints exposed in the current version, the handshake signing method, the message protocol, and the callback structure. The Host, Client ID, and signature in the examples are placeholders; replace them with the values assigned by the platform or agreed upon by both parties.

The same AK/SK can be used for both WebSocket and REST: WebSocket handles trading write operations and real-time subscription pushes; for scenarios without a persistent connection, you can use the equivalent REST endpoints for order create/cancel and position close (see the REST documentation).

Current WebSocket capabilities include:

  • Trading writes: order.create, order.cancel, position.close; currently connected to HYPERLIQUID and HABITTRADE.
  • Trading queries: order.query, position.query.
  • Real-time subscriptions: order.updated, position.updated, pushed per vault frame.
  • Account-level authorization: data.vaultAddress of every business op must belong to a vault operable by the current AK, otherwise VAULT_NOT_PERMITTED.

2. Base URL, Content-Type, Time/Number/Null Conventions​

2.1 General Conventions​

  • Base URL: pick the corresponding wss address from the environment table at the top; the trading endpoint path is fixed at /api/openapi/v1/ws.
  • Transport: UTF-8 JSON text frames; binary frames are closed. WebSocket does not use Content-Type or a request body.
  • Uplink frame limit: 5 KiB (exceeding returns UPLINK_TOO_LARGE, connection not closed); exceeding 64 KiB closes the connection (code 1009).
  • Authentication signs once during the handshake only; after the connection is established, no further auth is needed (see §4).
  • Handshake URL query params (clientId/timestamp/nonce/signature) should be UTF-8 percent-encoded.
  • This document only covers the trading WebSocket protocol available to partners.

2.2 Time Conventions​

  • Handshake timestamp: Unix seconds (string); accepts yyyy-MM-dd HH:mm:ss or a plain number (≥1e12 treated as milliseconds), valid window ±300 seconds.
  • Uplink request ts: Unix millisecond timestamp, must be strictly increasing (ts ≤ the cached previous ts → dropped with REQUEST_TS_STALE).
  • Downlink response/event ts, acceptedAt, and order/position createdAt/updatedAt/exchangeTime/serverTime: all epoch milliseconds.

2.3 Number & Null Conventions​

  • Trading numbers (quantity, price, leverage, slippage, USD value, fee, etc.) use decimal strings; do not parse them with lossy types such as JavaScript Number. leverage.value/rawUsd, maxSlippageBps, etc. are also decimal strings.
  • Quantity must be a multiple of sizeStep and limit price a multiple of priceTick (see the overview table in §3); otherwise INVALID_SIZE_STEP / INVALID_PRICE_TICK is returned.
  • Price fields are in USD per asset unless otherwise noted.
  • Downlink serialization omits fields whose value is null by default (see §5 Message Protocol).

3. Overview​

This document is for external partners integrating the OpenAPI trading WebSocket interface. The WebSocket interface supports order create/query/cancel, position query/close, and real-time order/position push subscriptions. The same AK/SK can be used for both WebSocket and REST.

ItemValue
WS endpointwss://<host>/api/openapi/v1/ws
TransportUTF-8 JSON text frames (binary frames are closed)
Protocol versionv field fixed to "1.0.0"
ExchangesHYPERLIQUID (HL, perpetual contracts) / HABITTRADE (HT, spot rebalancing vault)
Business opsorder.create / order.query / order.cancel / position.query / position.close
Subscription topicsorder.updated / position.updated (type=subscribe, op is the topic)
Quantity precisionHL sizeStep=0.001; HT sizeStep=1 (must be a multiple, otherwise INVALID_SIZE_STEP)
Price precisionHL priceTick=0.1; HT priceTick=0.01 (limit price must be a multiple)

Only one active connection is allowed per AK: opening a new connection with the same AK closes the old one (code 4001). The new connection does not inherit the old connection's subscriptions or in-flight requests.

4. Authentication & Handshake Signing​

WebSocket reuses the HTTP OpenAPI AK/SK: clientId is the AK and the Ed25519 private key is the SK. Signing happens only at the handshake; no auth afterwards.

Handshake Signing​

wss://<host>/api/openapi/v1/ws?clientId=<AK>&timestamp=<ts>&nonce=<nonce>&signature=<sig>
ParamDescription
clientIdAK (same Access Key as HTTP OpenAPI)
timestampUnix seconds (string). Accepts yyyy-MM-dd HH:mm:ss or a plain number (≥1e12 treated as ms). Valid window ±300 seconds
nonceRandom string (UUID recommended), one-time, must not repeat within 300 seconds
signatureEd25519 signature of the string to sign, lowercase hex (64 bytes ≡ 128 chars)

String to sign (three lines separated by \n):

<clientId>\n<timestamp>\n<nonce>

i.e. the UTF-8 bytes of clientId + "\n" + timestamp + "\n" + nonce are signed with Ed25519 and hex-encoded.

Any check failure → the handshake returns HTTP 401 and does not upgrade to WS. After a successful handshake, no further auth is required for the connection.

5. Message Protocol​

Serialization omits null fields. Allowed uplink type values: request / subscribe / unsubscribe.

{
"v": "1.0.0",
"type": "request",
"id": "order.create-4",
"op": "order.create",
"ts": 1787623689692,
"exchange": "HYPERLIQUID",
"data": { }
}
FieldRequiredDescription
vYesProtocol version, fixed 1.0.0
typeYesrequest / subscribe / unsubscribe
idYesRequest ID, unique per connection, used to match responses. Reuse on the same connection → DUPLICATE_REQUEST_ID
opYesFor request, the business op; for subscribe/unsubscribe, the topic/fixed string
tsYesClient time (ms), must be strictly increasing (ts ≤ cached previous ts → REQUEST_TS_STALE dropped)
exchangeNoHYPERLIQUID / HABITTRADE; omit only for order.query/position.query cross-exchange merged query
dataDepends on opBusiness params of each op, see §7/§8

The uplink frame limit is 5 KiB; exceeding returns UPLINK_TOO_LARGE (connection not closed); exceeding 64 KiB closes the connection (code 1009).

{
"v": "1.0.0",
"type": "response",
"id": "order.create-4",
"op": "order.create",
"ts": 1787623689692,
"code": "SUCCESS",
"message": "accepted",
"data": { }
}

code is an error code name (see §10); code=SUCCESS means success.

{
"v": "1.0.0",
"type": "event",
"topic": "position.updated",
"subscriptionId": "sub_52a6f1e5-6555-41b2-bc65-19eed11ea9fd",
"seq": 41,
"exchange": "HYPERLIQUID",
"ts": 1787638868358,
"data": { }
}

seq is monotonically increasing within a connection, used by the client for dedup/alignment, reset to zero on reconnect.

6. Connection, Rate Limit, Heartbeat & Close Codes​

ConventionDefaultDescription
Max uplink frame5 KiBExceeds → UPLINK_TOO_LARGE; over 64 KiB → 1009
Max downlink frame64 KiBExceeds → dropped
In-flight requests per connection10Duplicate id → DUPLICATE_REQUEST_ID; full → TOO_MANY_INFLIGHT_REQUESTS
Max subscriptions per connection4Re-subscribing the same exchange+topic overwrites the old (does not add quota)
Heartbeat Ping period30sServer sends Ping periodically
Pong timeout60sNo Pong for 60s → close code 4002

Rate limit (clientId + IP dimensions): IP 60/60s; clientId 120/1h. Either hit → RATE_LIMIT.

Close codes:

CodeMeaning
1000Normal close
1008Auth/config policy rejection; binary frame; transport error
1009Uplink frame over 64 KiB
4001Replaced by a new connection on the same AK
4002Pong timeout (no Pong for 60s)

7. HYPERLIQUID (HL) Business Endpoints​

General: data.vaultAddress is required for every business op; the server checks whether it is operable by the current AK. No permission → VAULT_NOT_PERMITTED; data.deniedVaultAddresses lists the denied addresses. vaultAddress is case-insensitive.

7.1 order.create​

HL supports MARKET and LIMIT order types.

Request data:

FieldRequiredDescription
vaultAddressYesVault address
symbolYesCoin
sideYesBUY / SELL
orderTypeYesMARKET / LIMIT (other → INVALID_REQUEST)
quantityYesCoin quantity, must be a multiple of sizeStep (0.001)
leverageYesLeverage
priceLIMIT requiredLimit price, must be a multiple of priceTick (0.1)
maxSlippageBpsMARKET requiredMarket slippage, 1–1000
timeInForceNoe.g. GTC / FrontendMarket
reduceOnlyNodefaults to false

Request example (market buy):

{
"v": "1.0.0",
"type": "request",
"id": "order.create-4",
"op": "order.create",
"ts": 1787623689692,
"exchange": "HYPERLIQUID",
"data": {
"vaultAddress": "0x7097732c73b74e94d6e8cc9e77c287757a17afdc",
"symbol": "ETH",
"side": "BUY",
"orderType": "MARKET",
"quantity": "0.01",
"leverage": "1",
"maxSlippageBps": "500",
"timeInForce": "FrontendMarket",
"reduceOnly": false
}
}

Response data:

FieldDescription
orderIdHYPERLIQUID:<exchangeOrderId>
exchangeOrderIdUpstream order id (same value used for cancel/query)
statusAlways PENDING on success
acceptedAtUpstream accept time (epoch ms)
encodedDataAlways null for HL (omitted)

Response example:

{
"v": "1.0.0",
"type": "response",
"id": "order.create-4",
"op": "order.create",
"ts": 1787623689692,
"code": "SUCCESS",
"message": "accepted",
"data": {
"orderId": "HYPERLIQUID:525764570803",
"exchangeOrderId": "525764570803",
"status": "PENDING",
"acceptedAt": 1787623689690
}
}

7.2 order.cancel​

Request data:

FieldRequiredDescription
vaultAddressYesVault address
exchangeOrderIdYesexchangeOrderId returned by create/query
symbolYesSame coin as the order

Request example:

{
"v": "1.0.0",
"type": "request",
"id": "order.cancel-3",
"op": "order.cancel",
"ts": 1787625671772,
"exchange": "HYPERLIQUID",
"data": {
"vaultAddress": "0x7097732c73b74e94d6e8cc9e77c287757a17afdc",
"exchangeOrderId": "525796683535",
"symbol": "ETH"
}
}

Response data:

FieldDescription
exchangeOrderIdSame as request
statusCANCELED

Response example:

{
"v": "1.0.0",
"type": "response",
"id": "order.cancel-3",
"op": "order.cancel",
"ts": 1787625672636,
"code": "SUCCESS",
"message": "success",
"data": {
"exchangeOrderId": "525796683535",
"status": "CANCELED"
}
}

7.3 position.close​

HL close supports full close only (PARTIAL → FEATURE_UNSUPPORTED).

Request data:

FieldRequiredDescription
vaultAddressYesVault address
symbolYesCoin
maxSlippageBpsYes1–1000
quantityNoOmitted/empty = full close; if provided, must be a multiple of sizeStep

Request example (full close):

{
"v": "1.0.0",
"type": "request",
"id": "position.close-2",
"op": "position.close",
"ts": 1787625645550,
"exchange": "HYPERLIQUID",
"data": {
"vaultAddress": "0x7097732c73b74e94d6e8cc9e77c287757a17afdc",
"symbol": "ETH",
"maxSlippageBps": "50"
}
}

Response data:

FieldDescription
orderIdHYPERLIQUID:<exchangeOrderId>
exchangeOrderIdUpstream order id
statusAlways PENDING on success
acceptedAtUpstream accept time (epoch ms)
encodedDataAlways null for HL (omitted)

Response example:

{
"v": "1.0.0",
"type": "response",
"id": "position.close-2",
"op": "position.close",
"ts": 1787625646517,
"code": "SUCCESS",
"message": "accepted",
"data": {
"orderId": "HYPERLIQUID:525796683535",
"exchangeOrderId": "525796683535",
"status": "PENDING",
"acceptedAt": 1787625646517
}
}

7.4 order.query​

Request data:

FieldRequiredDescription
vaultAddressYesVault address
exchangeOrderIdNoExchange order id
limitNo1–100; merged query default 10, max 100
startDate / endDateNoTime range
symbolNoCoin
statusNoUnified status: PENDING/PARTIALLY_FILLED/FILLED/CANCELED/REJECTED
exchangeNoOmit to merge HL/HT queries, take limit sorted by createdAt descending

Request example:

{
"v": "1.0.0",
"type": "request",
"id": "order.query-2",
"op": "order.query",
"ts": 1787629031208,
"exchange": "HYPERLIQUID",
"data": {
"vaultAddress": "0x7097732c73b74e94d6e8cc9e77c287757a17afdc",
"limit": 20
}
}

Response data.orders array, element fields:

FieldDescription
exchangeHYPERLIQUID
symbolCoin
tokenAddressAlways null for HL
exchangeOrderIdExchange id
sideBUY / SELL
orderTypeMARKET / LIMIT / CANCEL, etc.
quantity / filledQuantity / price / averagePrice / feeDecimal strings
statusPENDING / PARTIALLY_FILLED / FILLED / CANCELED / REJECTED
reduceOnlybool
createdAt / updatedAtepoch ms
vaultAddressVault address

Response example (data.orders** array, two shown):**

{
"v": "1.0.0",
"type": "response",
"id": "order.query-2",
"op": "order.query",
"ts": 1787629031344,
"code": "SUCCESS",
"message": "success",
"data": {
"orders": [
{
"exchange": "HYPERLIQUID",
"symbol": "ETH",
"tokenAddress": null,
"exchangeOrderId": null,
"side": null,
"orderType": "CANCEL",
"quantity": null,
"filledQuantity": null,
"price": null,
"averagePrice": null,
"status": "PENDING",
"reduceOnly": false,
"createdAt": 1787625672000,
"updatedAt": 1787625672000,
"fee": null,
"vaultAddress": "0x7097732c73b74e94d6e8cc9e77c287757a17afdc"
},
{
"exchange": "HYPERLIQUID",
"symbol": "ETH",
"tokenAddress": null,
"exchangeOrderId": "525796683535",
"side": "SELL",
"orderType": "MARKET",
"quantity": "0.018100000000000000",
"filledQuantity": null,
"price": "2507.800000000000000000",
"averagePrice": null,
"status": "FILLED",
"reduceOnly": true,
"createdAt": 1787625646000,
"updatedAt": 1787625650000,
"fee": "0.020528000000000000",
"vaultAddress": "0x7097732c73b74e94d6e8cc9e77c287757a17afdc"
}
]
}
}

7.5 position.query​

Request data:

FieldRequiredDescription
vaultAddressYesVault address
exchangeNoOmit to merge both exchanges

Request example:

{
"v": "1.0.0",
"type": "request",
"id": "position.query-1",
"op": "position.query",
"ts": 1787637839577,
"exchange": "HYPERLIQUID",
"data": {
"vaultAddress": "0x7097732c73b74e94d6e8cc9e77c287757a17afdc"
}
}

Response data.positions array, element fields (contract position):

FieldDescription
exchangeHYPERLIQUID
symbolCoin
tokenAddressAlways null for HL
sideLONG / SHORT (net position direction; SHORT = short)
quantityPosition size (decimal string)
entryPriceEntry price
markPriceAlways null for HL
unrealizedPnlUnrealized PnL
leverage{ type, value, rawUsd } (e.g. { "type": "cross", "value": "20", "rawUsd": null })
marginModeCROSS / ISOLATED
positionValue / marginUsed / returnOnEquity / maxLeverageDecimal strings
exchangeTime / serverTimeUpstream/server time (ms)

Response example:

{
"v": "1.0.0",
"type": "response",
"id": "position.query-1",
"op": "position.query",
"ts": 1787637839823,
"code": "SUCCESS",
"message": "success",
"data": {
"positions": [
{
"exchange": "HYPERLIQUID",
"symbol": "ETH",
"side": "LONG",
"quantity": "0.01",
"entryPrice": "2496.6",
"markPrice": null,
"unrealizedPnl": "0.131",
"leverage": { "type": "cross", "value": "20", "rawUsd": null },
"marginMode": "CROSS",
"positionValue": "25.097",
"marginUsed": "1.25485",
"returnOnEquity": "0.1049427221",
"maxLeverage": "25",
"exchangeTime": null,
"serverTime": 1787637839822
}
]
}
}

8. HABITTRADE Business Endpoints​

HT (HabitTrade, spot rebalancing vault) semantics differ from HL:

  • orderType (MARKET/LIMIT) is an order type, not a direction; direction is determined by side (BUY/SELL).
  • HT does not support cancel → order.cancel returns FEATURE_UNSUPPORTED.
  • Create/close ack carries encodedData (ABI payload to sign; the client signs and submits on-chain); HL always omits it.
  • Precision: sizeStep = 1, priceTick = 0.01.

8.1 order.create​

Request data:

FieldRequiredDescription
vaultAddressYesVault address
tokenAddressNo (recommended)Asset contract address
symbolYesAsset symbol
sideYesBUY / SELL, mapped to upstream action
orderTypeYesMARKET / LIMIT (type, not direction)
quantityYesShare quantity, must be a multiple of sizeStep (1)
maxSlippageBpsYesSlippage 1–1000
priceLIMIT requiredLimit price, must be a multiple of priceTick (0.01)

Request example (market buy):

{
"v": "1.0.0",
"type": "request",
"id": "order.create-ht-1",
"op": "order.create",
"ts": 1787751481326,
"exchange": "HABITTRADE",
"data": {
"vaultAddress": "0xd12041f181cbd0ec66b459de1f5ce219254c62df",
"tokenAddress": "0x804688f293c15a5454586217a69f1cb36c20e15b",
"symbol": "SPCHs",
"side": "BUY",
"quantity": "1",
"orderType": "MARKET",
"maxSlippageBps": "3000"
}
}

Response data:

FieldDescription
orderIdHABITTRADE:<exchangeOrderId>
exchangeOrderIdUpstream order id
statusFixed BUILT on success
acceptedAtUpstream accept time (epoch ms)
encodedDataABI payload to sign (0x… hex); client signs and submits on-chain

Response example:

{
"v": "1.0.0",
"type": "response",
"id": "order.create-ht-1",
"op": "order.create",
"ts": 1787751486434,
"code": "SUCCESS",
"message": "accepted",
"data": {
"orderId": "HABITTRADE:62f917df0a7f483e9783d8fd4b7b8115",
"exchangeOrderId": "62f917df0a7f483e9783d8fd4b7b8115",
"status": "BUILT",
"acceptedAt": 1787751486433,
"encodedData": "0xfca9bd9e0000000000000000000000...(ABI to sign, actual length is longer)"
}
}

8.2 order.cancel — Unsupported​

Response example:

{
"v": "1.0.0",
"type": "response",
"id": "order.cancel-ht-1",
"op": "order.cancel",
"ts": 1787626010000,
"code": "FEATURE_UNSUPPORTED",
"message": "HabitTrade does not support order cancel",
"data": null
}

8.3 position.close​

Request data:

FieldRequiredDescription
vaultAddressYesVault address
tokenAddressYesAsset contract address
symbolYesAsset to close
maxSlippageBpsYesSlippage 1–1000
quantityNo"-1" or omitted = full close (only valid for SELL); positive = partial close (must be a multiple of sizeStep)

Request example (full close, quantity omitted):

{
"v": "1.0.0",
"type": "request",
"id": "position.close-ht-1",
"op": "position.close",
"ts": 1787736302848,
"exchange": "HABITTRADE",
"data": {
"vaultAddress": "0xd12041f181cbd0ec66b459de1f5ce219254c62df",
"symbol": "SPCHs",
"tokenAddress": "0x804688f293c15a5454586217a69f1cb36c20e15b",
"maxSlippageBps": "50"
}
}

Response example:

{
"v": "1.0.0",
"type": "response",
"id": "position.close-ht-1",
"op": "position.close",
"ts": 1787736303000,
"code": "SUCCESS",
"message": "accepted",
"data": {
"orderId": "HABITTRADE:62f917df0a7f483e9783d8fd4b7b8115",
"exchangeOrderId": "62f917df0a7f483e9783d8fd4b7b8115",
"status": "BUILT",
"acceptedAt": 1787736302999,
"encodedData": "0xfca9bd9e0000000000000000000000...(ABI to sign)"
}
}

8.4 order.query​

Request data:

FieldRequiredDescription
vaultAddressYesVault address
exchangeOrderIdNoExchange order id (for HT, the transaction hash)
limitNoMerged query default 10, max 100
startDate / endDateNoUnix seconds or yyyy-MM-dd HH:mm:ss
symbolNoAsset contract address
tokenAddressNoAsset contract address
statusNoUnified status

Request example:

{
"v": "1.0.0",
"type": "request",
"id": "order.query-ht-1",
"op": "order.query",
"ts": 1787664502488,
"exchange": "HABITTRADE",
"data": {
"vaultAddress": "0xd12041f181cbd0ec66b459de1f5ce219254c62df",
"limit": 20
}
}

Response data.orders array, element fields:

FieldDescription
exchangeHABITTRADE
symbolAsset contract address
tokenAddressAsset contract address
exchangeOrderIdTransaction hash
sideBUY / SELL
orderTypeAlways null (no market/limit concept)
quantity / filledQuantity0
price / averagePriceFill price (same value)
statusFILLED / PENDING / ERROR / PARTIALLY_FILLED
reduceOnlytrue for SELL, false for BUY
createdAt / updatedAtepoch ms
feeAlways null
vaultAddressVault address

Response example:

{
"v": "1.0.0",
"type": "response",
"id": "order.query-ht-1",
"op": "order.query",
"ts": 1787664502653,
"code": "SUCCESS",
"message": "success",
"data": {
"orders": [
{
"exchange": "HABITTRADE",
"symbol": "0x804688f293c15a5454586217a69f1cb36c20e15b",
"tokenAddress": "0x804688f293c15a5454586217a69f1cb36c20e15b",
"exchangeOrderId": "0x66560a584e68f8f9385d35acb950bfc0285fc3e739b708b8d71d6da628e54e33",
"side": "BUY",
"orderType": null,
"quantity": "0",
"filledQuantity": "0",
"price": null,
"averagePrice": null,
"status": "PENDING",
"reduceOnly": false,
"createdAt": 1787385339000,
"updatedAt": 1787385399000,
"fee": null,
"vaultAddress": "0xd12041f181cbd0ec66b459de1f5ce219254c62df"
}
]
}
}

8.5 position.query​

Request data:

FieldRequiredDescription
vaultAddressYesVault address

Request example:

{
"v": "1.0.0",
"type": "request",
"id": "position.query-ht-1",
"op": "position.query",
"ts": 1787662054736,
"exchange": "HABITTRADE",
"data": {
"vaultAddress": "0xd12041f181cbd0ec66b459de1f5ce219254c62df"
}
}

Response data.positions array, element fields (vault portfolio):

FieldDescription
exchangeHABITTRADE
symbolAsset name
tokenAddressAsset contract address
sideAlways LONG (held by the vault)
quantityCurrent quantity
markPriceCurrent quote
entryPriceEntry price (from upstream)
unrealizedPnl / returnOnEquitynull (spot has none)
leverage{ "type": "cross", "value": "1", "rawUsd": "0" }
marginModeCROSS
positionValueCurrent USD value
marginUsed"0" (spot has no margin)
maxLeverage"1"
exchangeTime / serverTimems

Response example:

{
"v": "1.0.0",
"type": "response",
"id": "position.query-ht-1",
"op": "position.query",
"ts": 1787667041781,
"code": "SUCCESS",
"message": "success",
"data": {
"positions": [
{
"exchange": "HABITTRADE",
"symbol": "USDC",
"tokenAddress": "0xc879c018db60520f4355c26ed1a6d572cdac1815",
"side": "LONG",
"quantity": "16.435225",
"entryPrice": null,
"markPrice": "1",
"unrealizedPnl": null,
"leverage": { "type": "cross", "value": "1", "rawUsd": "0" },
"marginMode": "CROSS",
"positionValue": "16.435225",
"marginUsed": "0",
"returnOnEquity": null,
"maxLeverage": "1",
"exchangeTime": null,
"serverTime": 1787667041780
}
]
}
}

9. Subscription Endpoints​

type=subscribe, op is the topic (order.updated / position.updated), data.vaultAddresses is an array; exchange is HYPERLIQUID or HABITTRADE.

9.1 Subscribe Request & ack​

Request example (position subscription):

{
"v": "1.0.0",
"type": "subscribe",
"id": "position.updated-6",
"op": "position.updated",
"ts": 1787638868184,
"exchange": "HYPERLIQUID",
"data": {
"vaultAddresses": ["0x7097732c73b74e94d6e8cc9e77c287757a17afdc"]
}
}

ack (note the ack op is "subscribe", not the topic):

{
"v": "1.0.0",
"type": "subscribe",
"id": "position.updated-6",
"op": "subscribe",
"ts": 1787638868357,
"code": "SUCCESS",
"message": "subscribed",
"data": {
"subscription": {
"subscriptionId": "sub_52a6f1e5-6555-41b2-bc65-19eed11ea9fd",
"exchange": "HYPERLIQUID",
"topic": "position.updated"
}
}
}
  • data.vaultAddresses is required; each address must be operable by the AK. Any without permission → VAULT_NOT_PERMITTED; data.deniedVaultAddresses lists the denied addresses (does not affect existing subscriptions).
  • Re-subscribing the same exchange+topic overwrites the old subscription; only brand-new subscriptions check the limit (≤ 4).
  • For HT subscriptions, change exchange to HABITTRADE; handshake/heartbeat are the same as HL.

9.2 Event Frames​

Position event (one push immediately after subscribing with reason=INITIAL, then every 60s with reason=PERIODIC, one frame per vault, including empty snapshots):

{
"v": "1.0.0",
"type": "event",
"topic": "position.updated",
"subscriptionId": "sub_52a6f1e5-6555-41b2-bc65-19eed11ea9fd",
"seq": 41,
"exchange": "HYPERLIQUID",
"ts": 1787638868358,
"data": {
"vaultAddress": "0x7097732c73b74e94d6e8cc9e77c287757a17afdc",
"snapshot": true,
"reason": "INITIAL",
"stale": false,
"asOf": 1787638868357,
"positions": [
{
"exchange": "HYPERLIQUID",
"symbol": "ETH",
"side": "LONG",
"quantity": "0.01",
"entryPrice": "2496.6",
"markPrice": null,
"unrealizedPnl": "0.131",
"leverage": { "type": "cross", "value": "20", "rawUsd": null },
"marginMode": "CROSS",
"positionValue": "25.097",
"marginUsed": "1.25485",
"returnOnEquity": "0.1049427221",
"maxLeverage": "25",
"exchangeTime": null,
"serverTime": 1787638868357
}
]
}
}

Order event (one push immediately after subscribing, then every 60s, only in-flight PENDING/PARTIALLY_FILLED, one frame per vault):

{
"v": "1.0.0",
"type": "event",
"topic": "order.updated",
"subscriptionId": "sub_ac6ab7be-3581-4bb1-83bd-125a9a29ca07",
"seq": 45,
"exchange": "HYPERLIQUID",
"ts": 1787639052680,
"data": {
"vaultAddress": "0x7097732c73b74e94d6e8cc9e77c287757a17afdc",
"orders": [
{
"exchange": "HYPERLIQUID",
"symbol": "ETH",
"tokenAddress": null,
"exchangeOrderId": "525796683535",
"side": "BUY",
"orderType": "MARKET",
"quantity": "0.01",
"filledQuantity": null,
"price": "2500.1",
"averagePrice": null,
"status": "PENDING",
"reduceOnly": false,
"createdAt": 1787623689692,
"updatedAt": 1787623689692,
"fee": null,
"vaultAddress": "0x7097732c73b74e94d6e8cc9e77c287757a17afdc"
}
]
}
}

Position/order event element fields are the same as the corresponding exchange query endpoint: HL as §7.5/§7.4; HT as §8.5/§8.4.

9.3 Unsubscribe​

Request example:

{
"v": "1.0.0",
"type": "unsubscribe",
"id": "unsubscribe-7",
"op": "unsubscribe",
"ts": 1787638961103,
"data": {
"subscriptionIds": ["sub_52a6f1e5-6555-41b2-bc65-19eed11ea9fd"]
}
}

ack:

{
"v": "1.0.0",
"type": "unsubscribe",
"id": "unsubscribe-7",
"op": "unsubscribe",
"ts": 1787638961187,
"code": "SUCCESS",
"message": "unsubscribed",
"data": null
}

10. Error Codes​

codemessage
SUCCESSSuccess
INVALID_REQUESTInvalid request: JSON/field/business param missing or invalid
UPLINK_TOO_LARGEUplink frame exceeds the limit; shrink and resend
REQUEST_TS_STALERequest ts is stale or repeated
VAULT_NOT_PERMITTEDvaultAddress is not operable
INVALID_EXCHANGEUnsupported exchange
EXCHANGE_NOT_CONFIGUREDExchange account or required credentials not configured
SYMBOL_NOT_SUPPORTEDTrading pair/topic not enabled
TOO_MANY_INFLIGHT_REQUESTSIn-flight request limit reached for this connection
DUPLICATE_REQUEST_IDRequest ID reused on this connection
TOO_MANY_SUBSCRIPTIONSSubscription limit reached for this connection
RATE_LIMITToo many requests; retry later
DUPLICATE_REQUEST_CONFLICTSame clientOrderId with different request content
ORDER_NOT_FOUNDOrder not found
INVALID_TIME_RANGEHistorical order time range invalid or span exceeds 7 days
INSUFFICIENT_MARGINInsufficient margin or balance
INVALID_PRICE_TICKInvalid price precision
INVALID_SIZE_STEPInvalid quantity precision
REDUCE_ONLY_REJECTEDReduce-only request would increase the position, or the position has changed
EXCHANGE_REJECTEDExchange business rejection
UPSTREAM_TIMEOUTRetryable query request timeout
UPSTREAM_RESULT_UNKNOWNWrite result unknown; you must query first
UPSTREAM_UNAVAILABLEExchange temporarily unavailable
FEATURE_UNSUPPORTEDThe exchange does not support the requested capability
INTERNAL_ERRORUnexpected error

11. Order Status​

Unified statusDescription
PENDINGOrder accepted, not yet filled
PARTIALLY_FILLEDPartially filled (upstream may not have this status; OpenAPI maps it)
FILLEDFully filled
CANCELEDCanceled
EXPIREDExpired or terminated due to TTL (status retained, currently unused)
REJECTEDRejected by the exchange
ERRORGeneric error, used when the precise error cannot be distinguished

The order.updated event only actively pushes PARTIALLY_FILLED, FILLED, and CANCELED.

12. Java Connection Example​

Demo: connect + handshake signing + limit order + proactive Pong keepalive. AK masked, private key not printed. Pure JDK (java.net.http.WebSocket requires JDK 11+, Ed25519 requires JDK 15+, HexFormat requires JDK 17+).

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.util.Base64;
import java.util.HexFormat;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class HlWsExample {
private static final String CLIENT_ID = "ak_xxxxxxxxxxxxxxxxxxxxxxxxx";
private static final String PRIV_B64 = "<ed25519 PKCS#8 private key, base64(PEM body, no header/footer)>"; // SK, never log
private static final String BASE = "wss://<host>";
private static final String PATH = "/api/openapi/v1/ws";
private WebSocket ws;
private final Object sendLock = new Object();
private CompletableFuture<WebSocket> lastSend = null;
private final ScheduledExecutorService keepalive = Executors.newSingleThreadScheduledExecutor();

public static void main(String[] args) throws Exception {
PrivateKey sk = loadEd25519(PRIV_B64);
String ts = String.valueOf(Instant.now().getEpochSecond());
String nonce = UUID.randomUUID().toString();
String signature = HexFormat.of().formatHex(
sign(sk, (CLIENT_ID + "\n" + ts + "\n" + nonce).getBytes(StandardCharsets.UTF_8)));
String url = BASE + PATH + "?clientId=" + enc(CLIENT_ID) + "&timestamp=" + ts
+ "&nonce=" + enc(nonce) + "&signature=" + signature;
HlWsExample c = new HlWsExample();
WebSocket ws = HttpClient.newHttpClient().newWebSocketBuilder().buildAsync(URI.create(url), c.listener()).join();
c.ws = ws;
Thread.sleep(45_000);
c.keepalive.shutdownNow();
ws.sendClose(WebSocket.NORMAL_CLOSURE, "bye").get();
}

WebSocket.Listener listener() {
return new WebSocket.Listener() {
@Override public void onOpen(WebSocket webSocket) {
System.out.println("[open]"); HlWsExample.this.ws = webSocket; webSocket.request(1);
// limit order example
sendText("""
{"v":"1.0.0","type":"request","id":"order.create-4","op":"order.create",
"ts":1787623689692,"exchange":"HYPERLIQUID","data":{
"vaultAddress":"0x7097732c73b74e94d6e8cc9e77c287757a17afdc",
"symbol":"ETH","side":"BUY","orderType":"LIMIT","quantity":"0.01",
"price":"2500.1","leverage":"20","timeInForce":"GTC","reduceOnly":false}}""");
keepalive.scheduleAtFixedRate(HlWsExample.this::sendPong, 2, 20, TimeUnit.SECONDS);
}
@Override public CompletionStage<?> onText(WebSocket w, CharSequence data, boolean last) {
System.out.println("[rx] " + data); w.request(1); return CompletableFuture.completedStage(null);
}
@Override public CompletionStage<?> onClose(WebSocket w, int code, String reason) {
System.out.println("[close] code=" + code + ", reason=" + reason); return null;
}
@Override public void onError(WebSocket w, Throwable e) { System.err.println("[error] " + e); }
};
}

private void sendText(String text) {
synchronized (sendLock) {
CompletableFuture<WebSocket> base = (lastSend == null) ? CompletableFuture.completedFuture(ws) : lastSend;
lastSend = base.thenCompose(w -> w.sendText(text, true));
}
}
private void sendPong() {
try { synchronized (sendLock) {
CompletableFuture<WebSocket> base = (lastSend == null) ? CompletableFuture.completedFuture(ws) : lastSend;
lastSend = base.thenCompose(w -> w.sendPong(ByteBuffer.wrap("ka".getBytes(StandardCharsets.UTF_8))));
} } catch (Exception e) { System.err.println("pong err: " + e); }
}
private static PrivateKey loadEd25519(String base64) throws Exception {
return KeyFactory.getInstance("Ed25519").generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(base64)));
}
private static byte[] sign(PrivateKey sk, byte[] data) throws Exception {
Signature s = Signature.getInstance("Ed25519"); s.initSign(sk); s.update(data); return s.sign();
}
private static String enc(String v) { return URLEncoder.encode(v, StandardCharsets.UTF_8); }
}

Note: timestamp uses Unix seconds; nonce is recommended to be a UUID; signature is the 128-char lowercase hex of the Ed25519 signature. The server sends Pings periodically; the client is advised to also send Pongs proactively to avoid being closed after 60s with no Pong (4002). Only one active connection is kept per AK; a new connection causes the old one to be closed (4001).