Skip to content

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.

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",
    "subAccountId": "1867542890123456789",
    "status": ["filled", "cancelled"],
    "fromTime": 1704067200000,
    "toTime": 1704672000000,
    "limit": 50,
    "expiresAfter": 1704067500,
    "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"
subAccountIdstringYesSubaccount identifier
statusstring[]NoFilter by order status: ["started", "placed", "partially filled", "filled", "cancelling", "cancelled", "rejected", "modifying", "modified", "unknown"]
fromTimeintegerNoStart timestamp in milliseconds (inclusive, max 7-day range)
toTimeintegerNoEnd timestamp in milliseconds (inclusive, max 7-day range)
limitintegerNoMaximum number of orders to return (default: 50, max: 1000)
expiresAfterintegerNoOptional expiration timestamp in seconds
signatureobjectYesEIP-712 signature using SubAccountAction type
Important Notes:
  • This endpoint uses SubAccountAction EIP-712 type (no nonce required, only expiresAfter is optional)
  • Maximum time range between fromTime and toTime is 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 status to get orders in any status
  • Placed Orders Only: "status": ["placed"]
  • Historical Orders: "status": ["filled", "cancelled"] with time range
  • Recent Activity: Use fromTime without toTime for 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 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) {
    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 getOpenOrders requests 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

FieldTypeDescription
orderIdstringUnique order identifier (uint64 as string)
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)
statusstringOrder status (e.g., "placed", "filled", "cancelled")
timeInForcestringTime in force: "GTC", "IOC", or "FOK"
createdTimeintegerOrder creation timestamp (Unix milliseconds)
filledQuantitystringQuantity that has been filled
filledPricestringAverage fill price (VWAP for partial fills)
triggeredByLiquidationbooleanWhether order was triggered by liquidation
reduceOnlybooleanWhether order only reduces existing position
postOnlybooleanWhether 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 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

  • 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 fromTime and toTime is 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"]
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