Skip to content

Get Order History (WebSocket)

Retrieve order history for the authenticated subaccount through the WebSocket connection with comprehensive filtering, sorting, and pagination capabilities, providing access to orders in any status (open, filled, partiallyFilled, cancelled, rejected, expired) 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.

MethodPurposeWhen to Use
getOrderHistoryComprehensive order queryingInitial load, order history, filtered searches, reconciliation
Order Updates SubscriptionReal-time notificationsLive UI updates, event handling

Endpoint

ws.send() wss://api.synthetix.io/v1/ws/trade

Request

Request Format

{
  "id": "getorders-1",
  "method": "post",
  "params": {
    "action": "getOrderHistory",
    "status": ["open", "filled"],
    "symbol": "BTC-USDT",
    "fromTime": 1704067200000,
    "toTime": 1704153600000,
    "limit": 100,
    "offset": 0,
    "sortBy": "createdTime",
    "sortOrder": "desc",
    "subAccountId": "1867542890123456789",
    "nonce": 1704067200000,
    "signature": {
      "v": 28,
      "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
      "s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
    }
  }
}

Parameters

Request Parameters

ParameterTypeRequiredDescription
idstringYesClient-generated unique request identifier
methodstringYesMust be "post"
paramsobjectYesContains all parameters for the request

Params Object

ParameterTypeRequiredDescription
actionstringYesMust be "getOrderHistory"
statusstring[]NoFilter by order status: ["open", "filled", "partiallyFilled", "cancelled", "rejected", "started", "cancelling", "modifying", "unknown"]
symbolstringNoFilter orders by specific market symbol
fromTimeintegerNoStart timestamp in milliseconds (inclusive)
toTimeintegerNoEnd timestamp in milliseconds (inclusive)
limitintegerNoMaximum number of orders to return (default: 50, max: 1000)
offsetintegerNoNumber of orders to skip for pagination (default: 0)
sortBystringNoSort field: "createdTime", "updatedTime", "filledQuantity" (default: "createdTime")
sortOrderstringNoSort direction: "asc" or "desc" (default: "desc")
subAccountIdstringYesSubaccount identifier
nonceintegerYesIncrementing nonce (Unix ms timestamp as number)
signatureobjectYesEIP-712 signature

Filter Combinations

The endpoint supports powerful filter combinations:

  • All Orders: Omit status to get orders in any status
  • Open Orders Only: "status": ["open"]
  • Historical Orders: "status": ["filled", "cancelled"] with time range
  • Recent Activity: Use fromTime without toTime for orders since a specific time
  • Symbol-Specific: Combine symbol with any other filters

Response Format

Success Response

{
  "id": "getorders-1",
  "status": 200,
  "result": [
    {
      "orderId": "1958787130134106112",
      "symbol": "BTC-USDT",
      "side": "buy",
      "type": "LIMIT",
      "quantity": "10.0",
      "price": "45000.00",
      "triggerPrice": "",
      "triggerPriceType": "",
      "timeInForce": "GTC",
      "reduceOnly": false,
      "postOnly": false,
      "closePosition": false,
      "createdTime": 1755846234000,
      "updatedTime": 1755846234000,
      "filledQuantity": "10.0"
    },
    {
      "orderId": "1958787130134106113",
      "symbol": "ETH-USDT",
      "side": "sell",
      "type": "LIMIT",
      "quantity": "5.0",
      "price": "2800.00",
      "triggerPrice": "",
      "triggerPriceType": "",
      "timeInForce": "GTC",
      "reduceOnly": false,
      "postOnly": false,
      "closePosition": false,
      "createdTime": 1755846235000,
      "updatedTime": 1755846235000,
      "filledQuantity": "2.0"
    }
  ]
}

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 StructureNew 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, signer) {
    this.ws = ws;
    this.signer = signer;
    this.requestId = 0;
  }
 
  async getOrderHistory(options = {}) {
    const {
      status = null,
      symbol = null,
      fromTime = null,
      toTime = null,
      limit = 50,
      offset = 0,
      sortBy = "createdTime",
      sortOrder = "desc",
      subAccountId
    } = options;
 
    // Generate nonce
    const nonce = Date.now();
 
    // Create query signature
    const signature = await this.signGetOrders(options, nonce);
 
    // Build action object
    const action = {
      type: "getOrderHistory",
      limit,
      offset,
      sortBy,
      sortOrder
    };
 
    if (status) action.status = status;
    if (symbol) action.symbol = symbol;
    if (fromTime) action.fromTime = fromTime;
    if (toTime) action.toTime = toTime;
 
    // Build request
    const request = {
      id: `getorders-${Date.now()}`,
      method: "post",
      params: {
        action: "getOrderHistory",
        status,
        symbol,
        fromTime,
        toTime,
        limit,
        offset,
        sortBy,
        sortOrder,
        subAccountId: subAccountId.toString(),
        nonce,
        signature
      }
    };
 
    // Send and wait for response
    return this.sendRequest(request);
  }
 
  async signGetOrders(options, nonce) {
    const environment = process.env.ENVIRONMENT || 'testnet'; // 'mainnet' or 'testnet'
 
    const domain = {
      name: "Synthetix",
      version: "1",
      chainId: 1,
      verifyingContract: "0x0000000000000000000000000000000000000000"
    };
 
    const types = {
      GetOrders: [
        { name: "action", type: "GetOrdersAction" },
        { name: "subAccountId", type: "string" },
        { name: "nonce", type: "uint256" }
      ],
      GetOrdersAction: [
        { name: "action", type: "string" },
        { name: "status", type: "string" },
        { name: "symbol", type: "string" },
        { name: "fromTime", type: "uint256" },
        { name: "toTime", type: "uint256" },
        { name: "limit", type: "uint256" },
        { name: "offset", type: "uint256" },
        { name: "sortBy", type: "string" },
        { name: "sortOrder", type: "string" }
      ]
    };
 
    const message = {
      action: {
        type: "getOrderHistory",
        status: JSON.stringify(options.status || []),
        symbol: options.symbol || "",
        fromTime: options.fromTime || 0,
        toTime: options.toTime || 0,
        limit: options.limit || 50,
        offset: options.offset || 0,
        sortBy: options.sortBy || "createdTime",
        sortOrder: options.sortOrder || "desc"
      },
      subAccountId: options.subAccountId,
      nonce
    };
 
    const signature = await this.signer._signTypedData(domain, types, message);
    return ethers.utils.splitSignature(signature);
  }
 
  sendRequest(request) {
    return new Promise((resolve, reject) => {
      this.pendingRequests.set(request.requestId, { resolve, reject });
      this.ws.send(JSON.stringify(request));
 
      setTimeout(() => {
        if (this.pendingRequests.has(request.requestId)) {
          this.pendingRequests.delete(request.requestId);
          reject(new Error('Request timeout'));
        }
      }, 10000); // 10 second timeout
    });
  }
}
 
// Usage Examples
 
// Get All Open Orders (Legacy getOpenOrders equivalent)
async function getAllOpenOrders(query, subAccountId) {
  try {
    const result = await query.getOrderHistory({
      subAccountId,
      status: ["open"]
    });
    console.log(`Found ${result.orders.length} open orders`);
    return result.orders;
  } catch (error) {
    console.error('Failed to get open orders:', error);
    throw error;
  }
}
 
// Get Order History with Time Range
async function getOrderHistory(query, subAccountId, fromTime, toTime) {
  try {
    const result = await query.getOrderHistory({
      subAccountId,
      fromTime,
      toTime,
      limit: 100
    });
    console.log(`Found ${result.orders.length} historical orders`);
    return result.orders;
  } catch (error) {
    console.error('Failed to get order history:', error);
    throw error;
  }
}
 
// Filter by Symbol and Status
async function getOrderHistoryBySymbolAndStatus(query, subAccountId, symbol, status) {
  try {
    const result = await query.getOrderHistory({
      subAccountId,
      symbol,
      status,
      limit: 50
    });
    console.log(`Found ${result.orders.length} ${status} orders for ${symbol}`);
    return result.orders;
  } catch (error) {
    console.error(`Failed to get orders for ${symbol}:`, error);
    throw error;
  }
}
 
// Recent Orders with Custom Sorting
async function getRecentOrders(query, subAccountId, hoursBack = 24) {
  const fromTime = Date.now() - (hoursBack * 60 * 60 * 1000);
 
  try {
    const result = await query.getOrderHistory({
      subAccountId,
      fromTime,
      sortBy: "updatedTime",
      sortOrder: "desc",
      limit: 25
    });
    console.log(`Found ${result.orders.length} recent orders`);
    return result.orders;
  } catch (error) {
    console.error('Failed to get recent orders:', error);
    throw error;
  }
}
 
// Paginated Results
async function getPaginatedOrders(query, subAccountId, pageSize = 100) {
  const allOrders = [];
  let offset = 0;
  let hasMore = true;
 
  while (hasMore) {
    try {
      const result = await query.getOrderHistory({
        subAccountId,
        limit: pageSize,
        offset,
        sortBy: "createdTime",
        sortOrder: "asc"
      });
 
      allOrders.push(...result.orders);
      hasMore = result.pagination.hasMore;
      offset += pageSize;
 
      console.log(`Fetched ${result.orders.length} orders (total: ${allOrders.length})`);
    } catch (error) {
      console.error(`Failed to fetch page at offset ${offset}:`, error);
      break;
    }
  }
 
  return allOrders;
}

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 (open orders only)
    const result = await this.query.getOrderHistory({
      subAccountId: this.subAccountId,
      status: ["open"]
    });
 
    result.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 current = await this.query.getOrderHistory({
      subAccountId: this.subAccountId,
      status: ["open"]
    });
 
    // Sync any discrepancies
    this.orders.clear();
    current.orders.forEach(order => {
      this.orders.set(order.orderId, order);
    });
  }
}

Code Examples

Get All Open Orders

{
  "id": "getorders-open",
  "method": "post",
  "params": {
    "action": "getOrderHistory",
    "status": ["open"],
    "subAccountId": "1867542890123456789",
    "nonce": 1704067200000,
    "signature": {
      "v": 28,
      "r": "0x19480589384695193600abcdef19480589384695193600abcdef19480589384695193600abcdef19480589384695193600abcdef",
      "s": "0xabcdef19480589384695193600abcdef19480589384695193600abcdef19480589384695193600abcdef19480589384695193600"
    }
  }
}

Get Order History

{
  "id": "getorders-history",
  "method": "post",
  "params": {
    "action": "getOrderHistory",
    "fromTime": 1704067200000,
    "toTime": 1704153600000,
    "limit": 100,
    "subAccountId": "1867542890123456789",
    "nonce": 1704067200000,
    "signature": {
      "v": 28,
      "r": "0x19480589384695193600abcdef19480589384695193600abcdef19480589384695193600abcdef19480589384695193600abcdef",
      "s": "0xabcdef19480589384695193600abcdef19480589384695193600abcdef19480589384695193600abcdef19480589384695193600"
    }
  }
}

Use Cases

Trading Interface

  • Active Orders Management: Use "status": ["open"] to display orders that can be modified or cancelled
  • Order History: Use time ranges to show historical trading activity
  • Symbol-Specific Views: Filter by symbol for market-specific order management

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 getOpenOrders requests with appropriate filters
  • Efficient Pagination: Use offset-based pagination for large datasets
  • 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", "partiallyFilled"] }
 
// Get only successful orders
{ status: ["filled", "partiallyFilled"] }
 
// 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

FieldTypeDescription
orderIdstringUnique order identifier (64-bit unsigned integer as string)
clientOrderIdstringClient-provided order identifier (128-bit hex string with 0x prefix)
symbolstringTrading pair symbol (e.g., "BTC-USDT")
sidestringOrder side: "buy" or "sell"
typestringOrder type: "LIMIT", "MARKET", "STOP_LOSS", "TAKE_PROFIT"
quantitystringOriginal order quantity
pricestringOrder price (empty for market orders)
triggerPricestringTrigger price for conditional orders
triggerPriceTypestringTrigger price type: "mark", "last", or "index"
timeInForcestringTime in force: "GTC", "IOC", or "FOK"
reduceOnlybooleanWhether order only reduces existing position
postOnlybooleanWhether order must be maker (no immediate match)
createdTimeintegerOrder creation timestamp (Unix seconds)
updatedTimeintegerLast update timestamp (Unix seconds)
filledQuantitystringQuantity that has been filled
takeProfitOrderIdstringID of linked take-profit order (if exists)
stopLossOrderIdstringID of linked stop-loss order (if exists)
closePositionbooleanWhether order closes entire position

Multiple Fills Handling

When an order has multiple partial fills at different prices:

  • filledPrice: Volume-weighted average price (VWAP) of all fills
  • filledQuantity: 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

  • Use Symbol Filters: Request only needed symbols to reduce payload
  • Implement Pagination: Handle large order sets efficiently
  • 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

  • nonce must be a timestamp in milliseconds and must be increasing
  • Must be signed by account owner or authorized delegate
  • Subaccount ID must be valid and accessible
  • Time range must be valid (fromTime ≤ toTime)
  • Limit must be between 1 and 1000 (default: 50)
  • Offset must be non-negative
  • Status values must be valid: ["open", "filled", "partiallyFilled", "cancelled", "rejected", "started", "cancelling", "modifying", "unknown"]
  • Symbol must be a valid trading pair (if specified)
  • Sort fields must be valid: ["createdTime", "updatedTime", "filledQuantity"]
  • Sort order must be either "asc" or "desc"

Signing

All trading methods are signed using EIP-712. Each successful trading request will contain:

  • A piece of structured data that includes the sender address
  • A signature of the hash of that structured data, signed by the sender

For detailed information on EIP-712 signing, see EIP-712 Signing.

Nonce Management

The nonce system prevents replay attacks and ensures order uniqueness:

  • Use current timestamp in milliseconds as nonce
  • Each nonce must be greater than the previous one
  • Recommended: Use Date.now() or equivalent
  • If nonce conflicts occur, increment by 1 and retry
ErrorDescription
Invalid signatureEIP-712 signature validation failed
Invalid market symbolMarket symbol not recognized
Nonce already usedNonce must be greater than previous value
Rate limit exceededToo many requests in time window
Request expiredexpiresAfter timestamp has passed

Next Steps