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"
}