Skip to main content

WebSocket API

Real-time market data and order updates via WebSocket connections.

Connection URL

wss://stream.ballastmarkets.com

Authentication

Send an auth message immediately after connecting:

{
"type": "auth",
"api_key": "bmkt_live_abc123",
"signature": "<HMAC-SHA256 signature>",
"timestamp": 1678901234567
}

Signature computation:

message = f"{timestamp}auth"
signature = hmac.new(api_secret.encode(), message.encode(), hashlib.sha256).hexdigest()

Success response:

{
"type": "auth_success",
"user_id": "usr_abc123"
}

Subscribing to Channels

Market Data (Public)

Subscribe to order book updates:

{
"type": "subscribe",
"channels": ["orderbook:suez-apr2025", "trades:suez-apr2025"]
}

Order Book Update:

{
"type": "orderbook_update",
"market_id": "suez-apr2025",
"timestamp": "2025-03-15T10:35:12.456Z",
"bids": [
{"price": 0.870, "size": 5000},
{"price": 0.865, "size": 12000}
],
"asks": [
{"price": 0.875, "size": 3000},
{"price": 0.880, "size": 8000}
]
}

Trade Update:

{
"type": "trade",
"market_id": "suez-apr2025",
"timestamp": "2025-03-15T10:35:15.123Z",
"price": 0.872,
"size": 500,
"side": "buy"
}

Private Channels (Requires Auth)

Subscribe to your orders and positions:

{
"type": "subscribe",
"channels": ["orders", "positions"]
}

Order Update:

{
"type": "order_update",
"order_id": "ord_abc123",
"status": "partially_filled",
"filled_size": 300,
"remaining_size": 700,
"timestamp": "2025-03-15T10:36:00.000Z"
}

Position Update:

{
"type": "position_update",
"market_id": "suez-apr2025",
"size": 5300,
"average_price": 0.855,
"unrealized_pnl": 115.50
}

Placing Orders via WebSocket

{
"type": "order",
"market_id": "suez-apr2025",
"side": "buy",
"order_type": "limit",
"price": 0.870,
"size": 1000
}

Response:

{
"type": "order_ack",
"order_id": "ord_xyz789",
"status": "open",
"timestamp": "2025-03-15T10:37:00.000Z"
}

Heartbeats

Send a ping every 30 seconds to keep connection alive:

{"type": "ping"}

Server responds:

{"type": "pong", "timestamp": 1678901234567}

Example (Python)

import asyncio
import websockets
import json
import hmac
import hashlib
import time

API_KEY = "bmkt_live_abc123"
API_SECRET = "bmkt_secret_xyz789"
WS_URL = "wss://stream.ballastmarkets.com"

async def connect():
async with websockets.connect(WS_URL) as ws:
# Authenticate
timestamp = int(time.time() * 1000)
message = f"{timestamp}auth"
signature = hmac.new(API_SECRET.encode(), message.encode(), hashlib.sha256).hexdigest()

await ws.send(json.dumps({
"type": "auth",
"api_key": API_KEY,
"signature": signature,
"timestamp": timestamp
}))

auth_response = await ws.recv()
print(f"Auth: {auth_response}")

# Subscribe to market data
await ws.send(json.dumps({
"type": "subscribe",
"channels": ["orderbook:suez-apr2025", "trades:suez-apr2025"]
}))

# Listen for updates
async for message in ws:
data = json.loads(message)
print(f"Received: {data}")

asyncio.run(connect())

Rate Limits

  • Max 5 simultaneous connections per API key
  • Max 100 messages/second per connection
  • Auto-disconnect after 24 hours (must reconnect)

Error Messages

{
"type": "error",
"code": "INVALID_CHANNEL",
"message": "Channel 'invalid_channel' does not exist"
}

Next Steps