User Notifications
Receive authenticated order and balance events over WebSocket
See WebSocket Protocol for the full auth upgrade flow, heartbeat behavior, and close-code reference. This page focuses on user-specific events.
Authenticated users automatically receive order and balance notifications over the WebSocket. No separate subscription action is required once the connection has user-level auth.
Connect and authenticate
let accessToken = initialAccessToken;
const ws = new WebSocket(`wss://ws.agg.market/ws?appId=${appId}&token=${accessToken}`);
You will receive a connection acknowledgement:
{ "type": "connected", "appId": "your-app-id", "userId": "user-123" }
If the user signs in after the socket is already open, upgrade the connection in-place:
ws.send(JSON.stringify({ action: "authenticate", token: accessToken }));
// Response: { "type": "authenticated", "userId": "user-123" }
No initial state is sent on authentication. Use GET /execution/balances,
GET /execution/positions, and GET /execution/orders for the current user state, then use the
WebSocket to keep the UI fresh.
Events
Order submitted
Sent after a trade is accepted for execution:
{
"type": "order_submitted",
"venue": "predict",
"orderId": "order-abc-123",
"side": "buy",
"price": 0.55,
"size": 10,
"outcomeId": "vmo_123",
"timestamp": 1710705123456
}
Balance update
Sent when a trade changes the user's venue balance:
{
"type": "balance_update",
"venue": "predict",
"tradingBalanceCents": 50000,
"walletBalanceCents": 100000,
"timestamp": 1710705123456
}
Order event
Venue-agnostic stream of DAG lifecycle and terminal order status. Discriminate on the inner event field. Common shapes:
// Step lifecycle (drives progress UI)
{
"type": "order_event",
"event": "step_started",
"userId": "user-123",
"orderId": "order-abc-123",
"dagRunId": "dag_xyz",
"stepId": "execute-...",
"stepType": "submit-order",
"sequence": 3, // 1-based step index within the DAG
"totalSteps": 7, // total step count
"venue": "polymarket",
"timestamp": 1710705123456
}
// Terminal fill (triggers success toast + position refresh)
{
"type": "order_event",
"event": "filled", // or "partial_fill"
"userId": "user-123",
"orderId": "order-abc-123",
"venue": "polymarket",
"filledAmountRaw": "1000000",
"remainingAmountRaw": "0",
"timestamp": 1710705123456
}
// Failure (triggers error toast)
{
"type": "order_event",
"event": "failed",
"userId": "user-123",
"orderId": "order-abc-123",
"venue": "polymarket",
"errorReason": "Polymarket CLOB error (400): ...",
"timestamp": 1710705123456
}
event values: dag_started, dag_completed, dag_failed, dag_cancelled, step_started, step_completed, step_waiting, step_failed, step_retrying, filled, partial_fill, failed. The gateway broadcasts every event verbatim — switch on event and ignore types your UI doesn't care about.
Handle messages
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case "order_submitted":
// Legacy direct-trade event
addOrder({
id: msg.orderId,
venue: msg.venue,
side: msg.side,
price: msg.price,
size: msg.size,
});
break;
case "balance_update":
updateBalance(msg.venue, {
trading: msg.tradingBalanceCents / 100,
wallet:
msg.walletBalanceCents != null ? msg.walletBalanceCents / 100 : null,
});
break;
case "order_event":
// Venue-agnostic DAG + order lifecycle
switch (msg.event) {
case "step_started":
case "step_completed":
case "step_waiting":
// msg.sequence + msg.totalSteps available for "Step 3 of 7" UI
updateOrderProgress(
msg.orderId,
msg.event,
msg.stepType,
msg.sequence,
msg.totalSteps,
);
break;
case "filled":
case "partial_fill":
markOrderTerminal(msg.orderId, msg.event, msg.filledAmountRaw);
refreshPosition(msg.venue);
break;
case "failed":
case "dag_failed":
case "step_failed":
markOrderFailed(msg.orderId, msg.errorReason ?? msg.error);
break;
}
break;
case "error":
console.error(msg.message);
break;
}
};
Refresh-aware reconnect
If the socket closes and your access token may have expired, refresh it before reconnecting:
function connect() {
const ws = new WebSocket(`wss://ws.agg.market/ws?appId=${appId}&token=${accessToken}`);
ws.onclose = async () => {
try {
accessToken = await client.refreshAccessToken();
} catch {
await client.signOut();
return;
}
setTimeout(connect, 1000);
};
ws.onmessage = handleMessage;
}