WebSocket API — Connection & Trading

GoDark exposes a single multiplexed WebSocket endpoint. Clients authenticate once, establish an HPKE session, then send encrypted trading frames and/or subscribe to channels — all on the same socket.

the WebSocket origin/ws/v1

the configured host: the WebSocket origin/ws/v1. the configured host: ``. See Base URLs.

See the Orders Channel and Positions Channel docs for channel subscription detail. Prefer an official SDK for login + HPKE + order encryption.

Message format

Client → Server (JSON control / subscribe):

{ "id": "c-123", "op": "subscribe", "args": { "channel": "orders" } }
FieldDescription
idClient-supplied correlation id. The server echoes it on the matching response.
opOperation name (see op reference below).
argsOp-specific payload.

Server → Client (response to a JSON request):

{ "id": "c-123", "op": "subscribe", "code": 0, "data": { /* ... */ } }

Generic WebSocket request failures use code: 1. When the failure maps to a stable client product error, the response also includes error_code; for example unauthenticated private access uses error_code: 7001.

Server → Client (unsolicited stream event — channel push):

{ "channel": "orders", "type": "update", "seq": 42, "data": { /* ... */ } }

Encrypted trading requests, encrypted pushes, and HPKE setup use binary TradingWsBinaryFrame messages — not clear JSON order.place args. SDKs hide that framing. See Encryption & Transport.

Control Ops

opPurpose
loginAuthenticate the socket
logoutCleanly end the session
ping / pongHeartbeat
subscribe / unsubscribeManage channel subscriptions

op: login

Login is required before encrypted trading or private-channel subscriptions. It need not be the first frame when the client only subscribes to public channels (volume, open_interest, or funding_rate).

Official SDKs mint POST /api/v1/auth/token (client_credentials) and send the returned access_token — not id:secret:passphrase. A legacy opaque api_key (for example a custom host test-key-*) may still be sent as-is:

{
  "id": "c-1",
  "op": "login",
  "args": {
    "token": "<access_token>"
  }
}

Response:

{
  "id": "c-1",
  "op": "login",
  "code": 0,
  "data": {
    "account_id": "acct_<32-lowercase-hex-uuid>",
    "session_id": "sess_<32-lowercase-hex-uuid>",
    "conn_id": "123456789",
    "token_expires_at": "2026-04-20T10:45:00Z",
    "cancel_on_disconnect": false
  }
}
FieldNotes
conn_idNon-zero u64; binds the HPKE session.
account_idacct_ plus the authenticated account UUID as 32 lowercase hex digits (no hyphens). It is an identifier string, not the 32-byte HPKE account field.
session_idsess_ plus a fresh session UUID as 32 lowercase hex digits (no hyphens). Re-login as the same account rotates it.

After login, SDKs bind the authenticated account identity to an HPKE Base setup against the pinned sequencer public key, then encrypt place/cancel/modify.

Cancel-on-Disconnect

Opt in when logging in to have resting orders auto-cancelled if the socket drops:

{ "id": "c-1", "op": "login", "args": { "token": "...", "cancel_on_disconnect": true } }

op: logout

{ "id": "c-9", "op": "logout" }

Server responds with code: 0 and closes the socket.

op: ping / pong

Client pings:

{ "id": "c-hb-1", "op": "ping" }

Server responds:

{ "id": "c-hb-1", "op": "pong", "code": 0, "data": { "server_time_ns": 1839975000123000000 } }

The server also pings the client periodically; clients should reply with pong or the server will close the socket.

op: subscribe / unsubscribe

{ "id": "c-2", "op": "subscribe", "args": { "channel": "orders" } }
{ "id": "c-3", "op": "unsubscribe", "args": { "channel": "orders" } }

Channel reference:

ChannelScopeAuth
ordersAuthenticated accountVia op: login
positionsAuthenticated accountVia op: login
volumePublicNot required
open_interestPublicNot required
funding_ratePublicNot required

If a subscribe request mixes public and private channels before login, the public subscriptions may be established but the private portion fails with code: 1, error_code: 7001.

Account Ops

op: account.info is unsupported. Use encrypted POST /api/v1/account (get_account → account_margin_update).

Trading Ops (logical)

Logical place / cancel / modify fields match the REST schemas (Place Order, etc.). On the wire they are HPKE-sealed inner protobuf messages (PlaceOrderInput, …) selected by clear request_type — not an EdgeSequencerRequest envelope. Use GodarkClient (or equivalent) rather than inventing binary frames.

Public time-in-force strings: GTC, IOC, FOK, GTD. Optional slippage_bps on market / stop-market.

Sequence Numbers and Gap Recovery

Every channel emits a monotonically increasing seq per subscription per session. Clients should track the last seq they processed and, on any gap, unsubscribe + resubscribe to receive a fresh snapshot. Full protocol detail lives on each channel's page.

SDK example (JavaScript)

import { Environment, GodarkClient } from '@godark/sdk';

const client = new GodarkClient({
  apiKeyId: process.env.GODARK_API_KEY_ID,
  apiSecret: process.env.GODARK_API_SECRET,
  passphrase: process.env.GODARK_PASSPHRASE,
  environment: Environment.the configured host,
});

await client.connect(); // login + HPKE
await client.subscribe(['orders']);
const ack = await client.placeOrder({
  symbol: 'BTC-USDC-PERP',
  side: 'BUY',
  orderType: 'LIMIT',
  price: 95000,
  quantity: 0.1,
  timeInForce: 'GTC',
});
console.log(ack.orderId);
await client.disconnect();

REST vs WebSocket

AspectRESTWebSocket
LatencyHigher (TLS + HTTP per request)Lower (persistent multiplexed socket)
AuthBearer from POST /auth/tokenSDK mints access_token via POST /auth/token, then login with that JWT
Order bodiesOne-shot HPKE per requestSealed HPKE session (conn_id)
StreamingOn-demand encrypted POST snapshotsAuthoritative snapshots + live encrypted updates
Best forScripted one-offs via GodarkRestClientLong-running trading clients

Rate limits are shared with REST (see Rate Limits).

Reconnect and reconciliation

Reconnect establishes a new login and HPKE session; there is no public replay or resume cursor. After reconnect:

  1. fail or resolve any locally pending command promises according to the SDK;
  2. resubscribe to private channels;
  3. replace local orders and positions with the fresh authoritative snapshots;
  4. only then apply new live updates.

Do not retry a timed-out place blindly. Reconcile the open-orders snapshot and the original correlation/order identifiers first. Sequence gaps are recovered by resubscribing for a fresh snapshot, not by requesting historical replay.