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 to poll positions.
Python SDK
The Python SDK wraps this WebSocket in ArrowStreams.connect_order_stream(). See the WebSocket Streaming guide 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
Authentication Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
appID |
string | ✓ | Your application identifier |
token |
string | ✓ | User authentication token |
Connection Examples
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');
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
PONGevery 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_reconnectis 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)
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:
{
"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)
{
"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)
{
"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"
}
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
{
"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
{
"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" |
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() |