export TRADARA_API_KEY="tdr_live_..."
export FIRM_ID="660e8400-e29b-41d4-a716-446655440001"
export USER_ID="550e8400-e29b-41d4-a716-446655440000"
export ACCOUNT_ID="6ba7b810-9dad-11d1-80b4-00c04fd430c8"
wscat
Interactive shell, useful for spot-checking that a scope produces events.wscat -c "wss://api.tradara.com/v1/firm-ws" \
-H "X-Tradara-Api-Key: $TRADARA_API_KEY"
connected frame, paste:
{"type":"scope.set","scope":"account","user_id":"<USER_ID>","account_id":"<ACCOUNT_ID>"}
wscat -c "wss://api.tradara.com/v1/firm-ws?last_seq=1048393" \
-H "X-Tradara-Api-Key: $TRADARA_API_KEY"
Node.js (ws)
Minimal client that connects, subscribes to an account scope, tracks the highest seq, and reconnects with ?last_seq= on close 4002.
import WebSocket from "ws";
const BASE = "wss://api.tradara.com/v1/firm-ws";
const {
TRADARA_API_KEY,
USER_ID,
ACCOUNT_ID,
} = process.env;
let lastSeq = 0;
function connect() {
const url = lastSeq ? `${BASE}?last_seq=${lastSeq}` : BASE;
const ws = new WebSocket(url, {
headers: { "X-Tradara-Api-Key": TRADARA_API_KEY },
});
ws.on("open", () => console.log("[ws] open"));
ws.on("message", (raw) => {
const msg = JSON.parse(raw.toString());
if (msg.schema === "ws.firm.v1") {
// Event envelope
lastSeq = Math.max(lastSeq, msg.seq);
console.log(`[event] ${msg.type} seq=${msg.seq}`);
return;
}
switch (msg.type) {
case "connected":
ws.send(JSON.stringify({
type: "scope.set",
scope: "account",
user_id: USER_ID,
account_id: ACCOUNT_ID,
}));
break;
case "scope.confirmed":
console.log(`[ws] subscribed: ${msg.subscription}`);
break;
case "pong":
break;
case "error":
console.error(`[ws] error: ${msg.message}`);
break;
}
});
const ping = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "ping" }));
}
}, 25_000);
ws.on("close", (code) => {
clearInterval(ping);
console.warn(`[ws] closed code=${code} lastSeq=${lastSeq}`);
if (code === 4001) return; // fix auth before retrying
setTimeout(connect, code === 4002 ? 250 : 1000);
});
}
connect();
Python (websockets)
import asyncio
import json
import os
import websockets
BASE = "wss://api.tradara.com/v1/firm-ws"
API_KEY = os.environ["TRADARA_API_KEY"]
USER_ID = os.environ["USER_ID"]
ACCOUNT_ID = os.environ["ACCOUNT_ID"]
async def run(last_seq: int = 0) -> int:
url = f"{BASE}?last_seq={last_seq}" if last_seq else BASE
headers = [("X-Tradara-Api-Key", API_KEY)]
async with websockets.connect(url, additional_headers=headers) as ws:
async def keepalive():
while True:
await asyncio.sleep(25)
await ws.send(json.dumps({"type": "ping"}))
ping_task = asyncio.create_task(keepalive())
try:
async for raw in ws:
msg = json.loads(raw)
if msg.get("schema") == "ws.firm.v1":
last_seq = max(last_seq, msg["seq"])
print(f"[event] {msg['type']} seq={msg['seq']}")
continue
if msg["type"] == "connected":
await ws.send(json.dumps({
"type": "scope.set",
"scope": "account",
"user_id": USER_ID,
"account_id": ACCOUNT_ID,
}))
elif msg["type"] == "scope.confirmed":
print(f"[ws] subscribed: {msg['subscription']}")
elif msg["type"] == "error":
print(f"[ws] error: {msg['message']}")
finally:
ping_task.cancel()
return last_seq
async def main():
last_seq = 0
while True:
try:
last_seq = await run(last_seq)
except websockets.ConnectionClosedError as e:
if e.code == 4001:
raise
delay = 0.25 if e.code == 4002 else 1.0
await asyncio.sleep(delay)
asyncio.run(main())
Webhooks comparison
Use the Realtime WebSocket for dashboards and in-memory state. Use Webhooks for systems of record where durable, retried HTTP delivery matters more than latency. The
event_types you subscribe to via webhooks are the same type strings emitted on this stream — see the event catalog.
