Get Order History (WebSocket)
Retrieve order history for the authenticated subaccount through the WebSocket connection with comprehensive filtering capabilities, providing access to orders in any status (started, placed, partially filled, filled, cancelled, rejected) with flexible query options.
Request-Response vs Subscriptions
This method provides on-demand snapshots of order data with flexible filtering. For real-time updates when orders change, use SubAccount Updates Subscription.
| Method | Purpose | When to Use |
|---|---|---|
getOrderHistory | Comprehensive order querying | Initial load, order history, filtered searches, reconciliation |
| Order Updates Subscription | Real-time notifications | Live UI updates, event handling |
Endpoint
ws.send() wss://api.synthetix.io/v1/ws/tradeRequest
Request Format
{
"id": "getorders-1",
"method": "post",
"params": {
"action": "getOrderHistory",
"subAccountId": "1867542890123456789",
"status": ["filled", "cancelled"],
"fromTime": 1704067200000,
"toTime": 1704672000000,
"limit": 50,
"expiresAfter": 1704067500,
"signature": {
"v": 28,
"r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
}
}
}Parameters
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Client-generated unique request identifier |
method | string | Yes | Must be "post" |
params | object | Yes | Contains all parameters for the request |
Params Object
| Parameter | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Must be "getOrderHistory" |
subAccountId | string | Yes | Subaccount identifier |
status | string[] | No | Filter by order status: ["started", "placed", "partially filled", "filled", "cancelling", "cancelled", "rejected", "modifying", "modified", "unknown"] |
fromTime | integer | No | Start timestamp in milliseconds (inclusive, max 7-day range) |
toTime | integer | No | End timestamp in milliseconds (inclusive, max 7-day range) |
limit | integer | No | Maximum number of orders to return (default: 50, max: 1000) |
expiresAfter | integer | No | Optional expiration timestamp in seconds |
signature | object | Yes | EIP-712 signature using SubAccountAction type |
- This endpoint uses
SubAccountActionEIP-712 type (no nonce required, onlyexpiresAfteris optional) - Maximum time range between
fromTimeandtoTimeis 7 days (604,800,000 milliseconds)
EIP-712 Signature
const domain = {
name: "Synthetix",
version: "1",
chainId: 1,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
const types = {
SubAccountAction: [
{ name: "subAccountId", type: "uint256" },
{ name: "action", type: "string" },
{ name: "expiresAfter", type: "uint256" }
]
};
const message = {
subAccountId: "1867542890123456789",
action: "getOrderHistory",
expiresAfter: 0
};
const signature = await signer._signTypedData(domain, types, message);Filter Combinations
The endpoint supports powerful filter combinations:
- All Orders: Omit
statusto get orders in any status - Placed Orders Only:
"status": ["placed"] - Historical Orders:
"status": ["filled", "cancelled"]with time range - Recent Activity: Use
fromTimewithouttoTimefor orders since a specific time
Response Format
Success Response
{
"id": "getorders-1",
"status": 200,
"result": {
"status": "success",
"response": [
{
"orderId": "1958787130134106112",
"symbol": "BTC-USDT",
"side": "buy",
"type": "LIMIT",
"quantity": "10.0",
"price": "45000.00",
"status": "filled",
"timeInForce": "GTC",
"createdTime": 1755846234000,
"filledQuantity": "10.0",
"filledPrice": "45000.00",
"triggeredByLiquidation": false,
"reduceOnly": false,
"postOnly": false
},
{
"orderId": "1958787130134106113",
"symbol": "ETH-USDT",
"side": "sell",
"type": "LIMIT",
"quantity": "5.0",
"price": "2800.00",
"status": "partially filled",
"timeInForce": "GTC",
"createdTime": 1755846235000,
"filledQuantity": "2.0",
"filledPrice": "2800.00",
"triggeredByLiquidation": false,
"reduceOnly": false,
"postOnly": true
}
]
}
}Migration Note: Order Type Structure
Important: The API is transitioning from a nested order type structure to a flattened one for better EIP-712 compatibility:
| Old Structure | New Structure |
|---|---|
"type": "limit", "timeInForce": "gtc" | "orderType": "limitGtc" |
"type": "limit", "timeInForce": "ioc" | "orderType": "limitIoc" |
"type": "limit", "timeInForce": "alo" | "orderType": "limitAlo" |
"type": "market" | "orderType": "market" |
"type": "trigger", "triggerType": "stopLoss" | "orderType": "triggerSl" |
"type": "trigger", "triggerType": "takeProfit" | "orderType": "triggerTp" |
During the migration period, the API may return orders in either format. Clients should be prepared to handle both structures.
Error Response
{
"id": "getorders-1",
"status": 400,
"result": null,
"error": {
"code": 400,
"message": "Failed to get orders"
}
}Implementation Example
class OrderQuery {
constructor(ws) {
this.ws = ws;
this.pendingRequests = new Map();
}
async getOrderHistory(options = {}) {
const {
status = null,
fromTime = null,
toTime = null,
limit = 50,
subAccountId
} = options;
// Validate 7-day time range if both times provided
if (fromTime && toTime) {
const maxRange = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
if (toTime - fromTime > maxRange) {
throw new Error('Time range cannot exceed 7 days');
}
}
// Build request (no signature needed for read-only queries)
const request = {
id: `getorders-${Date.now()}`,
method: "post",
params: {
action: "getOrderHistory",
subAccountId: subAccountId.toString()
}
};
// Add optional filters
if (status) request.params.status = status;
if (fromTime) request.params.fromTime = fromTime;
if (toTime) request.params.toTime = toTime;
if (limit) request.params.limit = limit;
// Send and wait for response
return this.sendRequest(request);
}
sendRequest(request) {
return new Promise((resolve, reject) => {
this.pendingRequests.set(request.id, { resolve, reject });
this.ws.send(JSON.stringify(request));
// Handle timeout
setTimeout(() => {
if (this.pendingRequests.has(request.id)) {
this.pendingRequests.delete(request.id);
reject(new Error('Request timeout'));
}
}, 10000); // 10 second timeout
});
}
handleResponse(response) {
const pending = this.pendingRequests.get(response.id);
if (pending) {
this.pendingRequests.delete(response.id);
if (response.error) {
pending.reject(new Error(response.error.message));
} else {
pending.resolve(response.result.response);
}
}
}
}
// Usage Examples
// Get All Placed Orders
async function getAllPlacedOrders(query, subAccountId) {
try {
const result = await query.getOrderHistory({
subAccountId,
status: ["placed"]
});
console.log(`Found ${result.length} placed orders`);
return result;
} catch (error) {
console.error('Failed to get placed orders:', error);
throw error;
}
}
// Get Order History with Time Range
async function getOrderHistoryWithTimeRange(query, subAccountId, fromTime, toTime) {
try {
const result = await query.getOrderHistory({
subAccountId,
fromTime,
toTime,
limit: 100
});
console.log(`Found ${result.length} historical orders`);
return result;
} catch (error) {
console.error('Failed to get order history:', error);
throw error;
}
}
// Filter by Status
async function getOrderHistoryByStatus(query, subAccountId, status) {
try {
const result = await query.getOrderHistory({
subAccountId,
status,
limit: 50
});
console.log(`Found ${result.length} ${status} orders`);
return result;
} catch (error) {
console.error(`Failed to get orders with status ${status}:`, error);
throw error;
}
}
// Recent Orders
async function getRecentOrders(query, subAccountId, hoursBack = 24) {
const fromTime = Date.now() - (hoursBack * 60 * 60 * 1000);
try {
const result = await query.getOrderHistory({
subAccountId,
fromTime,
limit: 25
});
console.log(`Found ${result.length} recent orders`);
return result;
} catch (error) {
console.error('Failed to get recent orders:', error);
throw error;
}
}Integration with Subscriptions
Combine request-response queries with subscriptions for complete order management:
class OrderManager {
constructor(ws, query, subAccountId) {
this.ws = ws;
this.query = query;
this.subAccountId = subAccountId;
this.orders = new Map();
}
async initialize() {
// 1. Get initial state (placed orders only)
const orders = await this.query.getOrderHistory({
subAccountId: this.subAccountId,
status: ["placed"]
});
orders.forEach(order => {
this.orders.set(order.orderId, order);
});
// 2. Subscribe to real-time updates
this.ws.send(JSON.stringify({
id: "subscribe-orders",
method: "subscribe",
params: {
type: "orders",
subAccountId: this.subAccountId
}
}));
}
handleOrderUpdate(update) {
// Handle subscription events
const { eventType, orderId } = update.data;
if (eventType === 'cancelled' || eventType === 'filled') {
this.orders.delete(orderId);
} else {
this.orders.set(orderId, update.data);
}
}
async reconcile() {
// Periodic verification
const orders = await this.query.getOrderHistory({
subAccountId: this.subAccountId,
status: ["placed"]
});
// Sync any discrepancies
this.orders.clear();
orders.forEach(order => {
this.orders.set(order.orderId, order);
});
}
}Code Examples
Get All Open Orders
{
"id": "getorders-open",
"method": "post",
"params": {
"action": "getOrderHistory",
"status": ["placed"],
"subAccountId": "1867542890123456789"
}
}Get Order History
{
"id": "getorders-history",
"method": "post",
"params": {
"action": "getOrderHistory",
"fromTime": 1704067200000,
"toTime": 1704153600000,
"limit": 100,
"subAccountId": "1867542890123456789"
}
}Use Cases
Trading Interface
- Active Orders Management: Use
"status": ["placed"]to display orders that can be modified or cancelled - Order History: Use time ranges to show historical trading activity
Analytics and Reporting
- Performance Analysis: Filter by time range and status to calculate trading metrics
- Audit Trail: Retrieve complete order history for compliance and reporting
- Risk Management: Monitor open orders across all symbols
WebSocket Integration
- Legacy Compatibility: Replace
getOpenOrdersrequests with appropriate filters - Real-time Updates: Combine with WebSocket subscriptions for live order status updates
Advanced Filtering
Status Combinations
// Get all non-open orders
{ status: ["filled", "cancelled", "rejected", "partially filled"] }
// Get only successful orders
{ status: ["filled", "partially filled"] }
// Get failed orders
{ status: ["cancelled", "rejected"] }
// Get orders in transition states
{ status: ["cancelling", "modifying"] }Time-based Queries
// Orders from last 24 hours
{ fromTime: Date.now() - 86400000 }
// Orders in specific date range
{ fromTime: 1704067200000, toTime: 1704153600000 }
// Orders since specific time (no end)
{ fromTime: 1704067200000 }Order Object Structure
| Field | Type | Description |
|---|---|---|
orderId | string | Unique order identifier (uint64 as string) |
symbol | string | Trading pair symbol (e.g., "BTC-USDT") |
side | string | Order side: "buy" or "sell" |
type | string | Order type: "LIMIT", "MARKET", "STOP_LOSS", "TAKE_PROFIT" |
quantity | string | Original order quantity |
price | string | Order price (empty for market orders) |
status | string | Order status (e.g., "placed", "filled", "cancelled") |
timeInForce | string | Time in force: "GTC", "IOC", or "FOK" |
createdTime | integer | Order creation timestamp (Unix milliseconds) |
filledQuantity | string | Quantity that has been filled |
filledPrice | string | Average fill price (VWAP for partial fills) |
triggeredByLiquidation | boolean | Whether order was triggered by liquidation |
reduceOnly | boolean | Whether order only reduces existing position |
postOnly | boolean | Whether order must be maker (no immediate match) |
Multiple Fills Handling
When an order has multiple partial fills at different prices:
filledPrice: Volume-weighted average price (VWAP) of all fillsfilledQuantity: Total quantity filled across all partial fills- Example: Order for 10 units fills 3 @ $100 and 5 @ $102:
filledPrice: "101.25" (VWAP: (3×100 + 5×102) ÷ 8 = $101.25)filledQuantity: "8"
Implementation Notes
- Cache Strategically: Cache order data but validate freshness
- Monitor Changes: Use real-time updates for order state changes
- Handle Timeouts: Implement reasonable timeout values
- Error Recovery: Retry failed requests with backoff
Validation Rules
- Subaccount ID must be valid and accessible
- Time range must be valid (fromTime ≤ toTime)
- Maximum time range between
fromTimeandtoTimeis 7 days (604,800,000 milliseconds) - Limit must be between 1 and 1000 (default: 50)
- Status values must be valid:
["started", "placed", "partially filled", "filled", "cancelling", "cancelled", "rejected", "modifying", "modified", "unknown"]
| Error | Description |
|---|---|
| Invalid signature | EIP-712 signature validation failed |
| Invalid market symbol | Market symbol not recognized |
| Nonce already used | Nonce must be greater than previous value |
| Rate limit exceeded | Too many requests in time window |
| Request expired | expiresAfter timestamp has passed |
Next Steps
- SubAccount Updates Subscription - Real-time order change notifications (consolidated subscription)
- Get Positions - Position querying via WebSocket
- Cancel Orders - Order cancellation via WebSocket
- REST Alternative - REST API comparison