Skip to content

Place Orders (WebSocket)

Place orders through the WebSocket connection for lowest latency trading.

Endpoint

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

Request

Request Format

{
  "id": "place-orders-1",
  "method": "post",
  "params": {
    "action": "placeOrders",
    "orders": [
      {
        "symbol": "BTC-USDT",
        "side": "buy",
        "orderType": "limitGtc",
        "price": "50000.00",
        "triggerPrice": "",
        "quantity": "0.10",
        "reduceOnly": false,
        "isTriggerMarket": false,
        "clientOrderId": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
      }
    ],
    "grouping": "na",
    "subAccountId": "1867542890123456789",
    "nonce": 1704067200000,
    "expiresAfter": 1704067300000,
    "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 "placeOrders"
ordersarrayYesArray of order objects (minimum 1 order). Each order uses static fields: orderType, price, triggerPrice, isTriggerMarket
groupingstringNoOrder grouping: "na", "normalTpsl", "positionTpsl"
subAccountIdstringYesSubaccount identifier
nonceintegerYesIncrementing nonce (Unix ms timestamp as number)
expiresAfterintegerNoExpiration timestamp (Unix ms as number)
signatureobjectYesEIP-712 signature

Order Object

ParameterTypeRequiredDescription
symbolstringYesMarket symbol (e.g., "BTC-USDT")
sidestringYes"buy" or "sell"
orderTypestringYesOne of: limitGtc, limitIoc, limitAlo, market, triggerSl, triggerTp
pricestringYesPrice as string. Use empty string when not applicable (market orders and trigger market)
quantitystringYesOrder size as string
reduceOnlybooleanYesOnly reduce position
triggerPricestringYesTrigger price as string. Required for triggerSl and triggerTp; empty string otherwise
isTriggerMarketbooleanYesExecution type for trigger orders. true for market-on-trigger, false for limit-on-trigger. Must be false for non-trigger types
clientOrderIdstringNoClient-provided order ID. Must match pattern ^0x[a-fA-F0-9]{32}$ (32 hex chars after 0x)

Order Type Enum and Rules

  • limitGtc: price required; isTriggerMarket=false; triggerPrice=""
  • limitIoc: price required; isTriggerMarket=false; triggerPrice=""
  • limitAlo: price required; isTriggerMarket=false; triggerPrice=""
  • market: price=""; isTriggerMarket=false; triggerPrice=""
  • triggerSl: triggerPrice required; isTriggerMarket determines execution; if true then price="", if false then price required
  • triggerTp: same rules as triggerSl

Examples

Limit GTC

{
  "orderType": "limitGtc",
  "price": "50000",
  "triggerPrice": "",
  "isTriggerMarket": false
}

Market

{
  "orderType": "market",
  "price": "",
  "triggerPrice": "",
  "isTriggerMarket": false
}

Trigger SL (Market)

{
  "orderType": "triggerSl",
  "price": "",
  "triggerPrice": "45000",
  "isTriggerMarket": true
}

Trigger TP (Limit)

{
  "orderType": "triggerTp",
  "price": "44950",
  "triggerPrice": "45000",
  "isTriggerMarket": false
}

Response Format

Success Response

{
  "id": "place-orders-1",
  "status": "ok",
  "response": {
    "statuses": [
      {
        "resting": {
          "orderId": "1948058938469519360"
        }
      }
    ]
  },
  "request_id": "5ccf215d37e3ae6d",
  "timestamp": 1704067200000
}

Error Response

{
  "id": "place-orders-1",
  "status": "error",
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Failed to process order request",
    "details": {}
  },
  "request_id": "5ccf215d37e3ae6d",
  "timestamp": 1704067200000
}

Implementation Example

class TradingClient {
  constructor(ws, signer) {
    this.ws = ws;
    this.signer = signer;
    this.requestId = 0;
    this.pendingRequests = new Map();
  }
 
  generateClientOrderId() {
    const bytes = crypto.randomBytes(16);
    return `0x${bytes.toString('hex')}`;
  }
 
  async placeOrders(subAccountId, orders, grouping = "na") {
    // Ensure orders is an array
    const orderArray = Array.isArray(orders) ? orders : [orders];
 
    // Generate nonce
  const nonce = Date.now();
 
    // Create order signature
  const signature = await this.signOrders(subAccountId, orderArray, grouping, nonce);
 
    // Build request - WebSocket flattens all parameters into 'params'
    const request = {
      id: `post-${++this.requestId}`,
      method: "post",
      params: {
        action: "placeOrders",
        orders: orderArray,
        grouping,
        subAccountId: subAccountId.toString(),
        nonce: nonce,
        expiresAfter: nonce + 60000, // 1 minute expiry
        signature
      }
    };
 
    // Send and wait for response
    return this.sendRequest(request);
  }
 
  async signOrders(subAccountId, orders, grouping, nonce) {
    const domain = {
      name: "Synthetix",
      version: "1",
      chainId: 1,
      verifyingContract: "0x0000000000000000000000000000000000000000"
    };
 
    const types = {
      NewOrdersRequest: [
        { name: "subAccountId", type: "uint64" },
        { name: "orders", type: "string" }, // Stringified array
        { name: "grouping", type: "string" },
        { name: "nonce", type: "uint256" },
        { name: "expiresAfter", type: "uint256" }
      ]
    };
 
    const message = {
  subAccountId: BigInt(subAccountId),
  orders: JSON.stringify(orders),
  grouping,
  nonce: BigInt(nonce),
  expiresAfter: BigInt(nonce + 60000)
    };
 
// Using ethers v6 syntax
const signature = await this.signer.signTypedData(domain, types, message);
return ethers.Signature.from(signature);
  }
 
  sendRequest(request) {
    return new Promise((resolve, reject) => {
      // Store callback for response
      this.pendingRequests.set(request.id, { resolve, reject });
 
      // Send request
      this.ws.send(JSON.stringify(request));
 
      // Timeout after 30 seconds
      setTimeout(() => {
        if (this.pendingRequests.has(request.id)) {
          this.pendingRequests.delete(request.id);
          reject(new Error('Request timeout'));
        }
      }, 30000);
    });
  }
}
 
// Usage Examples
async function placeLimitOrder(client, subAccountId) {
  try {
    const result = await client.placeOrders(subAccountId, {
      symbol: "BTC-USDT",
      side: "buy",
      orderType: "limitGtc",
      price: "50000.00",
      triggerPrice: "",
      quantity: "0.10",
      reduceOnly: false,
      isTriggerMarket: false,
      clientOrderId: this.generateClientOrderId()
    });
 
    console.log('Order placed:', result);
  } catch (error) {
    console.error('Order failed:', error);
  }
}
async function placeMarketOrder(client, subAccountId) {
  try {
    const result = await client.placeOrders(subAccountId, {
      symbol: "ETH-USDT",
      side: "buy",
      orderType: "market",
      price: "",
      triggerPrice: "",
      quantity: "1.00",
      reduceOnly: false,
      isTriggerMarket: false
    });
    console.log('Market order placed:', result);
  } catch (error) {
    console.error('Market order failed:', error);
  }
}
async function placeMultipleOrders(client, subAccountId) {
  try {
    const orders = [
      {
        symbol: "BTC-USDT",
        side: "buy",
        orderType: "limitGtc",
    price: "50000.00",
        triggerPrice: "",
    quantity: "0.10",
        reduceOnly: false,
        isTriggerMarket: false
      },
      {
    symbol: "ETH-USDT",
    side: "sell",
    orderType: "limitAlo",
    price: "3200.00",
    triggerPrice: "",
    quantity: "1.00",
    reduceOnly: false,
    isTriggerMarket: false
      }
    ];
 
    const result = await client.placeOrders(subAccountId, orders);
    console.log('Multiple orders placed:', result);
  } catch (error) {
    console.error('Multiple orders failed:', error);
  }
}

Implementation Notes

  • Order parameters must validate locally before API submission
  • Client order IDs enable request tracking across WebSocket sessions
  • Authentication timestamps must be monotonically increasing per subaccount
  • EIP-712 domain separator must use "Synthetix" for WebSocket endpoints

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