# WebSocket Order Updates API

Real-time order updates with automatic reconnection for monitoring order status, executions, and rejections.

## Overview

The WebSocket Order Updates API provides instant notifications for all order-related events including placements, modifications, cancellations, executions, and rejections. This is a **push-only** connection — after authenticating, the server streams every order event for the connected user. There are no subscribe/unsubscribe messages.

Executions (fills) arrive as `ORDER_UPDATE` messages with `reportType: "Fill"`; there is no separate trade socket. Position updates are **not** delivered on this stream — use the [Positions API](positions.md) to poll positions.

!!! tip "Python SDK"
    The Python SDK wraps this WebSocket in `ArrowStreams.connect_order_stream()`. See the [WebSocket Streaming guide](../python-sdk/websocket-streaming.md) for SDK-level usage, event handlers, and examples.

### Key Features

- **Real-Time Order Updates**: Instant notifications for all order state changes
- **Push-Only Protocol**: No subscription messages required; all order events are delivered automatically
- **Automatic Reconnection**: Built-in exponential backoff retry mechanism
- **Heartbeat Monitoring**: Active connection health checks with configurable client-side intervals
- **Session-Based Authentication**: Secure connection using session tokens
- **Text-Based Protocol**: JSON messages for easy parsing and debugging

## Connection Setup

### WebSocket Endpoint

```
wss://order-updates.arrow.trade?appID=<APP_ID>&token=<TOKEN>
```

### Authentication Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `appID` | string | ✓ | Your application identifier |
| `token` | string | ✓ | User authentication token |

### Connection Examples

=== "Bash"

    ```bash
    websocat "wss://order-updates.arrow.trade?appID=<YOUR_APP_ID>&token=<YOUR_TOKEN>"
    ```

=== "JavaScript"

    ```javascript
    const APP_ID = "<YOUR_APP_ID>";
    const TOKEN = "<YOUR_TOKEN>";
    const wsUrl = `wss://order-updates.arrow.trade?appID=${APP_ID}&token=${TOKEN}`;
    const ws = new WebSocket(wsUrl);

    ws.onopen = () => console.log('Connected to order updates');

    ws.onmessage = (event) => {
        const update = JSON.parse(event.data);
        if (update.id) {
            console.log(`Order ${update.id}: ${update.orderStatus} (${update.reportType})`);
        }
    };

    ws.onerror = (err) => console.error('WebSocket error:', err);
    ws.onclose = () => console.log('Disconnected');
    ```

=== "Python"

    ```python
    import json
    import websocket

    APP_ID = "<YOUR_APP_ID>"
    TOKEN = "<YOUR_TOKEN>"
    ws_url = f"wss://order-updates.arrow.trade?appID={APP_ID}&token={TOKEN}"

    def on_message(ws, message):
        update = json.loads(message)
        if update.get("id"):
            print(f"Order {update['id']}: {update['orderStatus']} ({update['reportType']})")

    def on_open(ws):
        print("Connected to order updates")

    ws = websocket.WebSocketApp(ws_url, on_message=on_message, on_open=on_open)
    ws.run_forever()
    ```

## Connection Management

### Heartbeat Protocol

The connection maintains health through a client-side heartbeat mechanism. Custom clients should send a plain-text `PONG` message (not JSON) at a regular interval and treat prolonged read silence as a stale connection.

The Python SDK (`ConnectionConfig`) uses these defaults for all WebSocket streams (order, data, and HFT):

- **Client Ping**: Sends plain-text `PONG` every **3 seconds** (`ping_interval`)
- **Read Timeout**: **5 seconds** without an incoming message closes the connection and triggers reconnection (`read_timeout`)
- **Automatic Recovery**: Reconnects on timeout or connection loss when `enable_reconnect` is true

### Reconnection Strategy

Built-in exponential backoff with configurable parameters. Python SDK defaults (`ConnectionConfig`):

| Parameter | Default | Description |
|-----------|---------|-------------|
| `enable_reconnect` | `true` | Enable automatic reconnection |
| `max_reconnect_attempts` | 300 | Maximum reconnection attempts |
| `immediate_reconnect_attempts` | 3 | First N attempts with no delay |
| `max_reconnect_delay` | 5 seconds | Maximum delay between attempts |
| `ping_interval` | 3 seconds | Interval for client `PONG` messages |
| `read_timeout` | 5 seconds | Idle read timeout before reconnect |

**Backoff schedule (Python SDK):**

- Attempts 1–3: immediate (0 second delay)
- Attempt 4: 2 seconds (`2^1`, capped at 5)
- Attempt 5: 4 seconds (`2^2`, capped at 5)
- Attempt 6+: 5 seconds (capped at `max_reconnect_delay`)

!!! warning "Reconcile on reconnect"
    After reconnecting, fetch the full order book via `GET /user/orders` to catch any updates missed during the disconnection window.

## Order Update Messages

### Message Format

All order updates are JSON text frames with the following structure:

```json
{
  "updateType": "ORDER_UPDATE",
  "userID": "AJ0001",
  "accountID": "AJ0001",
  "exchange": "NFO",
  "symbol": "NIFTY27JAN26C25300",
  "id": "26012301000023",
  "price": "0.05",
  "quantity": "65",
  "product": "M",
  "orderStatus": "PENDING",
  "reportType": "PendingNew",
  "transactionType": "B",
  "order": "LMT",
  "cumulativeFillQty": "0",
  "fillShares": "0",
  "averagePrice": "0",
  "exchangeOrderID": "0",
  "cancelQuantity": "0",
  "orderTriggerPrice": "0",
  "validity": "DAY",
  "pricePrecision": "2",
  "tickSize": "0.05",
  "lotSize": "65",
  "token": "58695",
  "orderTime": "2026-01-23T13:40:53",
  "orderSource": "WEB",
  "leavesQuantity": "65"
}
```

### Additional Examples

#### Exchange Ack (Order Open)

```json
{
  "updateType": "ORDER_UPDATE",
  "userID": "AJ0001",
  "accountID": "AJ0001",
  "exchange": "NFO",
  "symbol": "NIFTY02DEC25C26100",
  "id": "25120202000010",
  "price": "34",
  "quantity": "75",
  "product": "M",
  "orderStatus": "OPEN",
  "reportType": "NewAck",
  "transactionType": "B",
  "order": "MKT",
  "cumulativeFillQty": "0",
  "fillShares": "0",
  "averagePrice": "0",
  "exchangeOrderID": "1400000055208129",
  "cancelQuantity": "0",
  "orderTriggerPrice": "0",
  "validity": "DAY",
  "pricePrecision": "2",
  "tickSize": "0.05",
  "lotSize": "75",
  "token": "46799",
  "orderTime": "2025-12-02T10:16:16",
  "exchangeUpdateTime": "2025-12-02T10:16:16",
  "exchangeTime": "2025-12-02T10:16:16",
  "orderSource": "WEB",
  "isAck": true,
  "leavesQuantity": "75"
}
```

#### Fill (Order Complete)

```json
{
  "updateType": "ORDER_UPDATE",
  "userID": "AJ0001",
  "accountID": "AJ0001",
  "exchange": "NFO",
  "symbol": "NIFTY02DEC25C26100",
  "id": "25120202000010",
  "price": "34",
  "quantity": "75",
  "product": "M",
  "orderStatus": "COMPLETE",
  "reportType": "Fill",
  "transactionType": "B",
  "order": "MKT",
  "cumulativeFillQty": "75",
  "fillShares": "75",
  "averagePrice": "34",
  "exchangeOrderID": "1400000055208129",
  "cancelQuantity": "0",
  "orderTriggerPrice": "0",
  "validity": "DAY",
  "pricePrecision": "2",
  "tickSize": "0.05",
  "lotSize": "75",
  "token": "46799",
  "orderTime": "2025-12-02T10:16:16",
  "exchangeUpdateTime": "2025-12-02T10:16:16",
  "exchangeTime": "2025-12-02T10:16:16",
  "orderSource": "WEB",
  "isAck": true,
  "leavesQuantity": "0"
}
```

!!! note "Partial vs full fill"
    Both partial and full fills use `reportType: "Fill"`. Distinguish them by checking `orderStatus` (`OPEN` or `PARTIALLY_FILLED` for partial, `COMPLETE` for full) and `leavesQuantity` (remaining unfilled quantity).

#### Rejection

```json
{
  "updateType": "ORDER_UPDATE",
  "userID": "AJ0001",
  "accountID": "AJ0001",
  "exchange": "NSE",
  "symbol": "IDEA-EQ",
  "id": "25120202000012",
  "rejectReason": "The price lies outside the DPR range",
  "price": "7.5",
  "quantity": "2",
  "product": "I",
  "orderStatus": "REJECTED",
  "reportType": "Rejected",
  "transactionType": "B",
  "order": "LMT",
  "cumulativeFillQty": "0",
  "fillShares": "0",
  "averagePrice": "0",
  "exchangeOrderID": "0",
  "cancelQuantity": "0",
  "remarks": "234",
  "validity": "DAY",
  "pricePrecision": "2",
  "tickSize": "0.01",
  "lotSize": "1",
  "token": "14366",
  "orderTime": "2025-12-02T11:32:31",
  "exchangeUpdateTime": "2025-12-02T11:32:31",
  "exchangeTime": "2025-12-02T11:32:31",
  "orderSource": "WEB",
  "isAck": true,
  "leavesQuantity": "0"
}
```

#### Cancellation

```json
{
  "updateType": "ORDER_UPDATE",
  "userID": "AJ0001",
  "accountID": "AJ0001",
  "exchange": "NSE",
  "symbol": "IDEA-EQ",
  "id": "25120202000013",
  "price": "10",
  "quantity": "10",
  "product": "I",
  "orderStatus": "CANCELLED",
  "reportType": "Canceled",
  "transactionType": "B",
  "order": "LMT",
  "cumulativeFillQty": "0",
  "fillShares": "0",
  "averagePrice": "0",
  "exchangeOrderID": "1100000029104706",
  "cancelQuantity": "10",
  "remarks": "234",
  "validity": "DAY",
  "pricePrecision": "2",
  "tickSize": "0.01",
  "lotSize": "1",
  "token": "14366",
  "orderTime": "2025-12-02T11:33:07",
  "exchangeUpdateTime": "2025-12-02T11:33:07",
  "exchangeTime": "2025-12-02T11:33:07",
  "orderSource": "WEB",
  "isAck": true,
  "leavesQuantity": "0"
}
```

### Field Reference

| Field | Type | Description | Example |
|-------|------|-------------|---------|
| `updateType` | string | Message type (always `"ORDER_UPDATE"`) | `"ORDER_UPDATE"` |
| `userID` | string | User identifier | `"AJ0001"` |
| `accountID` | string | Trading account identifier | `"AJ0001"` |
| `exchange` | string | Exchange code | `"NFO"`, `"NSE"`, `"BSE"`, `"BFO"`, `"MCXFO"` |
| `symbol` | string | Trading symbol | `"NIFTY27JAN26C25300"` |
| `id` | string | Unique order identifier | `"26012301000023"` |
| `price` | string | Order price | `"0.05"` |
| `quantity` | string | Order quantity | `"65"` |
| `product` | string | Product type | `"I"` (Intraday), `"C"` (Delivery), `"M"` (Normal/F&O) |
| `orderStatus` | string | Current order status | See Order Status table |
| `reportType` | string | Type of update report | See Report Types table |
| `transactionType` | string | Buy or Sell | `"B"` (Buy), `"S"` (Sell) |
| `order` | string | Order type | `"LMT"`, `"MKT"`, `"SL-LMT"`, `"SL-MKT"` |
| `cumulativeFillQty` | string | Total filled quantity across all fills | `"0"` |
| `fillShares` | string | Shares filled in this update | `"0"` |
| `averagePrice` | string | Average execution price | `"0"` |
| `exchangeOrderID` | string | Exchange-assigned order ID | `"0"` |
| `cancelQuantity` | string | Cancelled quantity | `"0"` |
| `orderTriggerPrice` | string | Trigger price for stop orders | `"0"` |
| `validity` | string | Order validity | `"DAY"`, `"IOC"` |
| `pricePrecision` | string | Decimal precision for price | `"2"` |
| `tickSize` | string | Minimum price increment | `"0.05"` |
| `lotSize` | string | Trading lot size | `"65"` |
| `token` | string | Instrument token | `"58695"` |
| `orderTime` | string | Order timestamp (ISO format) | `"2026-01-23T13:40:53"` |
| `orderSource` | string | Order entry source | `"WEB"`, `"API"`, `"MOBILE"` |
| `leavesQuantity` | string | Remaining unfilled quantity | `"65"` |
| `rejectReason` | string | Rejection reason (present when `orderStatus` is `REJECTED`) | `"The price lies outside the DPR range"` |
| `remarks` | string | Custom order tag set via place/modify (max 16 characters) | `"strategy_1"` |
| `exchangeUpdateTime` | string | Last update timestamp from the exchange (ISO format, present on acked orders) | `"2025-12-02T10:16:16"` |
| `exchangeTime` | string | Exchange system timestamp (ISO format) | `"2025-12-02T10:16:16"` |
| `isAck` | boolean | Whether the order has been acknowledged by the exchange | `true` |
| `disclosedQuantity` | string | Disclosed quantity for iceberg orders | `"0"` |
| `fillPrice` | string | Fill price for this execution (present on fill updates) | `"34"` |
| `marketProtection` | string | Market protection percentage applied to the order | `"0"` |

!!! note "triggerPrice vs orderTriggerPrice"
    When placing or modifying an order, the request field is `triggerPrice`. In order update messages (and `GET /user/orders` responses), the same value appears as `orderTriggerPrice`.

### Report Types

The `reportType` field indicates the type of order update:

| Report Type | Description | Example Scenario |
|-------------|-------------|------------------|
| `PendingNew` | Order submitted, awaiting exchange acknowledgement | New order placed |
| `NewAck` | Order accepted and acknowledged by exchange | Order becomes active (`OPEN`) |
| `Fill` | Execution occurred (partial or full) | Quantity filled; check `orderStatus` and `leavesQuantity` to distinguish partial from complete |
| `Replaced` | Order modified successfully | Price or quantity changed |
| `Canceled` | Order cancelled | User or system cancellation |
| `Rejected` | Order rejected by exchange or RMS | Insufficient margin, price outside DPR, etc. |
| `Expired` | Order expired | End of trading session |
| `Triggered` | Stop order triggered | Trigger price reached |

### Order Status Values

| Status | Description | Terminal State |
|--------|-------------|----------------|
| `PENDING` | Order submitted, awaiting exchange | No |
| `OPEN` | Active order in market | No |
| `PARTIALLY_FILLED` | Partially executed, remaining quantity still open | No |
| `COMPLETE` | Fully executed | Yes |
| `CANCELLED` | Cancelled by user/system | Yes |
| `REJECTED` | Rejected by exchange or RMS | Yes |
| `TRIGGER_PENDING` | Stop order waiting for trigger price | No |
| `AFTER_MARKET_ORDER_REQ_RECEIVED` | AMO order queued for next session | No |

### Order Type Codes

| Code | Full Name | Description |
|------|-----------|-------------|
| `LMT` | Limit | Order at specified price or better |
| `MKT` | Market | Plain `MKT` is disabled by default on the place-order API — use `mpp: true` for market-style routing (Upper Limit / DPR by instrument) |
| `SL-LMT` | Stop Loss Limit | Stop order with limit price |
| `SL-MKT` | Stop Loss Market | Stop order at market price |

### Transaction Type Codes

| Code | Full Name |
|------|-----------|
| `B` | Buy |
| `S` | Sell |

### Product Type Codes

| Code | Full Name | Description |
|------|-----------|-------------|
| `I` | Intraday | Same-day position closure (auto-squared off near session end) |
| `C` | Cash / Delivery | Equity delivery orders (T+1 settlement) |
| `M` | Normal / Margin | F&O orders including MCXFO (standard margin) |

### Exchange Codes

| Code | Description |
|------|-------------|
| `NSE` | National Stock Exchange — Equity |
| `NFO` | NSE Futures & Options |
| `BSE` | Bombay Stock Exchange — Equity |
| `BFO` | BSE Futures & Options |
| `MCXFO` | MCX Futures & Options |

## Troubleshooting

| Issue | Cause | Solution |
|-------|--------|----------|
| **Connection Fails** | Invalid appID/token | Verify credentials are current and valid |
| **Frequent Disconnects** | Network instability | Check network quality, increase timeout |
| **Missing Updates** | Connection dropped | Fetch order status via `GET /user/orders` on reconnect |
| **Duplicate Updates** | Network retry | Deduplicate using order `id` + `orderTime` |
| **High CPU Usage** | Too many handlers | Optimize callback functions, batch updates |
| **Memory Leaks** | Handlers not cleaned | Remove handlers when components unmount |
| **Wrong Field Names** | Using old documentation | Use `id` instead of `orderId`, `symbol` instead of `tradingSymbol` |
| **String vs Number** | Field type mismatch | Parse string fields to numbers: `parseInt()`, `parseFloat()` |
