GaiaExGaiaEx
API

User Data WebSocket Stream

Private WebSocket stream for real-time positions, open orders, and account balances on GaiaEx. Requires API key authentication.

Connection

WS wss://openapi.gaiaex.com/ws/user/{address}

Path Parameters:

ParameterTypeDescription
addressstringUser's wallet address

Authentication

The WebSocket accepts either an API-key HMAC signature (for bots and algo clients) or a session token (for mobile/web app sessions). HMAC is recommended for order-submission workloads because it amortises the TLS + HTTP-upgrade cost across every order sent on the same connection.

Option A: HMAC (API keys, recommended for trading)

Send the same three X-GAIAEX-* headers you use for REST during the WebSocket upgrade. Signing recipe on the handshake is ts + "GET" + path (body empty), where path is /ws/user/{address} exactly. The API key must carry the trade permission to submit order actions.

X-GAIAEX-APIKEY:    <api_key>
X-GAIAEX-TIMESTAMP: <unix_ms>
X-GAIAEX-SIGNATURE: HMAC_SHA256(api_secret, <ts> + "GET" + "/ws/user/<address>")

Option B: Session token (mobile/web app)

  1. Subprotocol header (recommended):

    Sec-WebSocket-Protocol: Bearer.<session_token>
  2. Authorization header (where supported by your WS client):

    Authorization: Bearer <session_token>

Connection Example (Python)

import asyncio
import websockets
import json

SESSION_TOKEN = "your_session_token"
ADDRESS = "0xYourAddress"

async def listen():
    uri = f"wss://openapi.gaiaex.com/ws/user/{ADDRESS}"
    async with websockets.connect(
        uri,
        subprotocols=[f"Bearer.{SESSION_TOKEN}"]
    ) as ws:
        async for message in ws:
            data = json.loads(message)
            if data["type"] == "update":
                print(f"Balance: {data['balance']['available']}")
                print(f"Positions: {len(data['positions'])}")
                print(f"Open orders: {len(data['orders'])}")

asyncio.run(listen())

Connection Example (JavaScript)

const ADDRESS = '0xYourAddress';
const SESSION_TOKEN = 'your_session_token';

const ws = new WebSocket(
  `wss://openapi.gaiaex.com/ws/user/${ADDRESS}`,
  [`Bearer.${SESSION_TOKEN}`]
);

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.type === 'update') {
    console.log('Balance:', data.balance.available);
    console.log('Positions:', data.positions.length);
    console.log('Orders:', data.orders.length);
  }
};

Message Types

Update (type: "update")

Pushed whenever positions, orders, or balances change. Also sent periodically as a heartbeat even if no changes occurred.

{
  "type": "update",
  "positions": [
    {
      "coin": "ETH",
      "szi": "0.1",
      "entryPx": "3500.00",
      "positionValue": "350.00",
      "unrealizedPnl": "12.50",
      "leverage": { "type": "cross", "value": 10 },
      "liquidationPx": "3150.00"
    }
  ],
  "orders": [
    {
      "orderId": 987654321,
      "symbol": "ETH",
      "side": "BUY",
      "type": "LIMIT",
      "origQty": "0.1",
      "price": "3400.00",
      "status": "NEW"
    }
  ],
  "spot_orders": [],
  "balance": {
    "usdc_balance": "1000.00",
    "hl_confirmed_bal": "1000.00",
    "pending_deposit": "0.00",
    "available": "850.00",
    "unrealized_pnl": "12.50",
    "margin_used": "150.00",
    "maintenance_margin": "35.00",
    "total_ntl_pos": "350.00",
    "cross_margin_ratio": "0.041",
    "cross_account_leverage": "0.35"
  },
  "spot_balances": [
    {
      "coin": "USDC",
      "total": "500.00",
      "hold": "0.00",
      "available": "500.00"
    }
  ],
  "timestamp": 1743508800000,
  "sequence": 42
}
FieldTypeDescription
positionsarrayAll open perpetual positions (empty array if none)
ordersarrayAll open perpetual orders
spot_ordersarrayAll open spot orders
balanceobjectPerpetual account balance summary
spot_balancesarraySpot token balances
timestampintServer timestamp in ms
sequenceintMonotonically increasing sequence number per connection

Heartbeat (type: "heartbeat")

Sent when the data feed is temporarily unavailable. Confirms the connection is alive.

{
  "type": "heartbeat",
  "address": "0xYourAddress",
  "timestamp": 1743508800000,
  "sequence": 43
}

Error (type: "error")

Sent when a temporary data fetch error occurs. The connection remains open and will resume normal updates.

{
  "type": "error",
  "message": "Temporary data fetch error. Retrying..."
}

Order Actions via WebSocket

You can also place and manage orders through the WebSocket connection, avoiding the overhead of establishing new HTTP connections.

Sending an Order Action

Send a JSON message with an action field:

{
  "action": "place_order",
  "client_id": "my-order-001",
  "data": {
    "user_address": "0xYourAddress",
    "symbol": "ETH",
    "is_buy": true,
    "size": "0.1",
    "price": "3500.00",
    "order_type": "limit"
  }
}

Supported Actions

ActionDescription
place_orderPlace a new order
cancel_orderCancel an order by ID
cancel_allCancel all orders (optionally by symbol)
close_positionClose a position
set_tpslSet take-profit / stop-loss
modify_orderModify an existing order

Response Messages

Acknowledgement (immediate):

{
  "type": "order_ack",
  "client_id": "my-order-001",
  "action": "place_order",
  "timestamp": 1743508800000
}

Result (after processing):

{
  "type": "order_result",
  "client_id": "my-order-001",
  "success": true,
  "data": {
    "orderId": 987654322,
    "status": "open",
    "symbol": "ETH"
  }
}

Error result:

{
  "type": "order_result",
  "client_id": "my-order-001",
  "success": false,
  "error": {
    "code": 400,
    "detail": "Insufficient margin"
  }
}

Ping / Pong

To keep the connection alive, send a text message "ping". The server will respond with "pong".

→ "ping"
← "pong"

TIP

Most WebSocket libraries handle ping/pong frames automatically. The text-based ping/pong is an additional application-level mechanism.

Terminal Walkthrough

Step 1: Connect with wscat

# Replace with your actual address and session token
$ wscat -c wss://openapi.gaiaex.com/ws/user/0xA6E3c04eF78427b5B53F43CDBA881d7E15B0bccD \
  -s "Bearer.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Connected (press CTRL+C to quit)

# Initial state snapshot arrives immediately:
< {"type":"update","positions":[{"coin":"ETH","szi":"0.5","entryPx":"3400.00",...}],"orders":[],"balance":{"available":"850.00",...},"timestamp":...}

# Place an order via WebSocket:
> {"action":"place_order","client_id":"test-001","data":{"user_address":"0xA6E3...","symbol":"ETH","is_buy":true,"size":"0.1","price":"3450.00","order_type":"limit"}}
< {"type":"order_ack","client_id":"test-001","action":"place_order","timestamp":...}
< {"type":"order_result","client_id":"test-001","success":true,"data":{"orderId":123456,"status":"open"}}

# Updated state with the new order:
< {"type":"update","positions":[...],"orders":[{"orderId":123456,"symbol":"ETH","side":"BUY","price":"3450.00",...}],...}

# Cancel the order:
> {"action":"cancel_order","client_id":"test-002","data":{"user_address":"0xA6E3...","symbol":"ETH","order_id":123456}}
< {"type":"order_result","client_id":"test-002","success":true,...}

Step 2: Python — Full Event Loop with Order Management

import asyncio
import json
import websockets

SESSION_TOKEN = "your_session_token"
ADDRESS = "0xYourAddress"

async def trading_loop():
    uri = f"wss://openapi.gaiaex.com/ws/user/{ADDRESS}"
    async with websockets.connect(uri, subprotocols=[f"Bearer.{SESSION_TOKEN}"]) as ws:

        # Send keepalive pings
        async def ping_loop():
            while True:
                await ws.send("ping")
                await asyncio.sleep(20)

        ping_task = asyncio.create_task(ping_loop())

        try:
            # Place order via WebSocket
            order_msg = json.dumps({
                "action": "place_order",
                "client_id": "bot-001",
                "data": {
                    "user_address": ADDRESS,
                    "symbol": "ETH",
                    "is_buy": True,
                    "size": "0.05",
                    "price": "3400.00",
                    "order_type": "limit",
                },
            })
            await ws.send(order_msg)
            print("Order sent")

            # Process all messages
            async for raw in ws:
                if raw == "pong":
                    continue
                msg = json.loads(raw)

                if msg["type"] == "order_ack":
                    print(f"ACK: {msg['client_id']}")
                elif msg["type"] == "order_result":
                    if msg["success"]:
                        print(f"Order filled/placed: {msg['data']}")
                    else:
                        print(f"Order error: {msg['error']}")
                elif msg["type"] == "update":
                    bal = msg.get("balance", {})
                    print(f"Balance: {bal.get('available', 'N/A')} | "
                          f"Positions: {len(msg.get('positions', []))} | "
                          f"Orders: {len(msg.get('orders', []))}")
        finally:
            ping_task.cancel()

asyncio.run(trading_loop())