WebSocket API — Connection & Trading

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

wss://api.godarkdex.com/ws/v1

See the Orders Channel and Positions Channel docs for channel subscription detail.

Message format

Client → Server (request):

{ "id": "c-123", "op": "order.place", "args": { /* op-specific */ } }
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 request):

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

On error, code is non-zero and a message field is present.

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

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

Responses carry id + op. Stream events carry channel + type + seq. Never both — clients can route on the presence of id vs channel.

Control Ops

opPurpose
loginAuthenticate the socket with a bearer token
logoutCleanly end the session
ping / pongHeartbeat
subscribe / unsubscribeManage channel subscriptions

op: login

Must be the first frame on any new connection. All other ops return 401 not_authenticated until login succeeds.

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

Response:

{
  "id": "c-1",
  "op": "login",
  "code": 0,
  "data": {
    "account_id": "acct_01HXYZ",
    "session_id": "sess_01HXYZ",
    "token_expires_at": "2026-04-20T10:45:00Z",
    "cancel_on_disconnect": false
  }
}

Re-login / Token Refresh

Send a fresh op: login before token_expires_at to rotate the token in place without dropping subscriptions. The server also emits an auth_expired event ~60s before expiry as a reminder.

Cancel-on-Disconnect

Opt in when logging in to have all your 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 every 20s; clients should reply with pong (same shape, client as sender) within 10s 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 accountBearer (via op: login)
positionsAuthenticated accountBearer (via op: login)
instrumentsPublicNot required
transparencyPublicNot required

Account Ops

op: account.info

Fetch balance, VIP tier, and account-level config. Mirrors GET /account — same response body. See Account Info for the full schema.

{ "id": "c-20", "op": "account.info" }

Trading Ops

All trading ops mirror the REST endpoints exactly — same request body, same validation, same response data. Sharing order-management code between REST and WS is a non-goal for the server but a free win for the client.

op: order.place

{
  "id": "c-10",
  "op": "order.place",
  "args": {
    "symbol": "BTC-USDC-PERP",
    "side": "buy",
    "type": "limit",
    "quantity": 0.1,
    "price": 95000,
    "time_in_force": "GTC",
    "client_order_id": "my-order-001"
  }
}

Response:

{
  "id": "c-10",
  "op": "order.place",
  "code": 0,
  "data": {
    "order_id": "ord_01HXYZ12345",
    "client_order_id": "my-order-001",
    "status": "NEW"
  }
}

See Place Order for the full request schema and risk-check semantics.

op: order.cancel

Either order_id or client_order_id — exactly one.

{
  "id": "c-11",
  "op": "order.cancel",
  "args": { "order_id": "ord_01HXYZ12345" }
}

op: order.modify

{
  "id": "c-12",
  "op": "order.modify",
  "args": {
    "order_id": "ord_01HXYZ12345",
    "price": 96000,
    "quantity": 0.15
  }
}

Queue-priority rules are identical to REST — see Modify Order.

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.

JavaScript Example

const ws = new WebSocket('wss://api.godarkdex.com/ws/v1');
let id = 0;
const next = () => `c-${++id}`;

ws.onopen = () => {
  ws.send(JSON.stringify({ id: next(), op: 'login', args: { token: ACCESS_TOKEN } }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  if (msg.id && msg.op === 'login' && msg.code === 0) {
    ws.send(JSON.stringify({ id: next(), op: 'subscribe', args: { channel: 'orders' } }));
    ws.send(JSON.stringify({
      id: next(),
      op: 'order.place',
      args: {
        symbol: 'BTC-USDC-PERP',
        side: 'buy',
        type: 'limit',
        quantity: 0.1,
        price: 95000,
        time_in_force: 'GTC',
        client_order_id: 'my-order-001',
      },
    }));
  }

  if (msg.channel === 'orders') {
    console.log('orders event', msg.type, msg.seq, msg.data);
  }
};

setInterval(() => ws.send(JSON.stringify({ id: next(), op: 'ping' })), 20000);

REST vs WebSocket

AspectRESTWebSocket
LatencyHigher (TLS + HTTP per request)Lower (persistent multiplexed socket)
AuthBearer header per requestOne op: login per session
StreamingGET snapshots onlySnapshot + incremental updates on channels
Best forOne-off calls, cold startsLong-running trading clients

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