> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agg.market/llms.txt
> Use this file to discover all available pages before exploring further.

# User Notifications

> Receive authenticated order and balance events over WebSocket

<Info>
  See [WebSocket Protocol](/api/websocket) for the full auth upgrade flow, heartbeat behavior, and
  close-code reference. This page focuses on user-specific events.
</Info>

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

```typescript theme={null}
let accessToken = initialAccessToken;

const ws = new WebSocket(`wss://ws.agg.market/ws?appId=${appId}&token=${accessToken}`);
```

You will receive a connection acknowledgement:

```json theme={null}
{ "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:

```typescript theme={null}
ws.send(JSON.stringify({ action: "authenticate", token: accessToken }));
// Response: { "type": "authenticated", "userId": "user-123" }
```

<Note>
  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.
</Note>

## Events

### Order submitted

Sent after a trade is accepted for execution:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```json theme={null}
// 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

```typescript theme={null}
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:

```typescript theme={null}
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;
}
```

## Related

<CardGroup cols={2}>
  <Card title="WebSocket Protocol" icon="gear" href="/api/websocket">
    Full auth upgrade, heartbeat, error, and reconnect reference.
  </Card>

  <Card title="Real-Time Orderbook" icon="code" href="/recipes/websocket-orderbook">
    Outcome-level orderbook subscriptions on the same socket connection.
  </Card>

  <Card title="Real-Time Charts" icon="brackets-curly" href="/recipes/websocket-charts">
    Build live charts from orderbook and trade events.
  </Card>

  <Card title="Token Refresh" icon="key" href="/recipes/token-refresh">
    Session renewal patterns for REST and WebSocket clients.
  </Card>
</CardGroup>
