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 */ } }
| Field | Description |
|---|---|
id | Client-supplied correlation id. The server echoes it on the matching response. |
op | Operation name (see op reference below). |
args | Op-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
op | Purpose |
|---|---|
login | Authenticate the socket with a bearer token |
logout | Cleanly end the session |
ping / pong | Heartbeat |
subscribe / unsubscribe | Manage 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:
| Channel | Scope | Auth |
|---|---|---|
orders | Authenticated account | Bearer (via op: login) |
positions | Authenticated account | Bearer (via op: login) |
instruments | Public | Not required |
transparency | Public | Not 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
| Aspect | REST | WebSocket |
|---|---|---|
| Latency | Higher (TLS + HTTP per request) | Lower (persistent multiplexed socket) |
| Auth | Bearer header per request | One op: login per session |
| Streaming | GET snapshots only | Snapshot + incremental updates on channels |
| Best for | One-off calls, cold starts | Long-running trading clients |
Rate limits are shared with REST (see Rate Limits).