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,
        "postOnly": false,
        "isTriggerMarket": false,
        "clientOrderId": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
        "closePosition": false
      }
    ],
    "grouping": "na",
    "subAccountId": "1867542890123456789",
    "nonce": 1704067200000,
    "expiresAfter": 1704067300,
    "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 seconds)
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
postOnlybooleanConditionalWhether order must be maker (no immediate match). Only used for limitGtc orders. Must be false for all other order types
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
closePositionbooleanYesClose entire position when triggered (TP/SL orders only). 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)

Note on postOnly: The postOnly field is a request parameter but is not included in the EIP-712 signed Order type. It is processed server-side after signature verification. The EIP-712 Order type contains: symbol, side, quantity, orderType, price, triggerPrice, reduceOnly, isTriggerMarket, clientOrderId, closePosition.

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

The response contains a statuses array with one entry per order. Each status can be one of: resting, filled, canceled, or error.

Resting Order (Accepted)

{
  "id": "place-orders-1",
  "status": 200,
  "result": {
    "statuses": [
      {
        "resting": {
          "id": "1948058938469519360"
        }
      }
    ]
  }
}

Filled Order (Immediately Executed)

{
  "id": "place-orders-1",
  "status": 200,
  "result": {
    "statuses": [
      {
        "filled": {
          "id": "1948058938469519360",
          "totalSize": "0.10",
          "avgPrice": "50000.00"
        }
      }
    ]
  }
}

Order Error

{
  "id": "place-orders-1",
  "status": 200,
  "result": {
    "statuses": [
      {
        "error": "Insufficient margin"
      }
    ]
  }
}
Response Fields:
FieldTypeDescription
resting.idstringOrder ID for accepted orders
filled.idstringOrder ID for filled orders
filled.totalSizestringTotal filled quantity
filled.avgPricestringAverage execution price
errorstringError message if order failed
Notes:
  • Each order in the request gets one status object in the response array
  • Order statuses are returned in the same order as the request
  • A 200 response can contain individual order errors in the statuses array

Request Error Response

{
  "id": "place-orders-1",
  "status": 400,
  "error": {
    "code": 400,
    "message": "Failed to process order request"
  }
}

Implementation Example

import { ethers } from 'ethers';
 
async function placeOrders(ws, signer, subAccountId, orders, grouping = "na") {
  const nonce = Date.now();
  const expiresAfter = nonce + 60000;
 
  // EIP-712 signature
  const domain = {
    name: "Synthetix",
    version: "1",
    chainId: 1,
    verifyingContract: "0x0000000000000000000000000000000000000000"
  };
 
  const types = {
    PlaceOrders: [
      { name: "subAccountId", type: "uint256" },
      { name: "orders", type: "Order[]" },
      { name: "grouping", type: "string" },
      { name: "nonce", type: "uint256" },
      { name: "expiresAfter", type: "uint256" }
    ],
    Order: [
      { name: "symbol", type: "string" },
      { name: "side", type: "string" },
      { name: "orderType", type: "string" },
      { name: "price", type: "string" },
      { name: "triggerPrice", type: "string" },
      { name: "quantity", type: "string" },
      { name: "reduceOnly", type: "bool" },
      { name: "isTriggerMarket", type: "bool" },
      { name: "clientOrderId", type: "string" },
      { name: "closePosition", type: "bool" }
    ]
  };
 
  const message = {
    subAccountId: BigInt(subAccountId),
    orders: Array.isArray(orders) ? orders : [orders],
    grouping,
    nonce: BigInt(nonce),
    expiresAfter: BigInt(expiresAfter)
  };
 
  const sig = await signer.signTypedData(domain, types, message);
  const signature = ethers.Signature.from(sig);
 
  // Send request
  ws.send(JSON.stringify({
    id: `place-orders-${Date.now()}`,
    method: "post",
    params: {
      action: "placeOrders",
      orders: message.orders,
      grouping,
      subAccountId,
      nonce,
      expiresAfter,
      signature: { v: signature.v, r: signature.r, s: signature.s }
    }
  }));
}
 
// Usage: Limit order
await placeOrders(ws, signer, "1867542890123456789", {
  symbol: "BTC-USDT",
  side: "buy",
  orderType: "limitGtc",
  price: "50000.00",
  triggerPrice: "",
  quantity: "0.10",
  reduceOnly: false,
  postOnly: false,
  isTriggerMarket: false,
  clientOrderId: "0x" + crypto.randomBytes(16).toString('hex'),
  closePosition: false
});
 
// Usage: Market order
await placeOrders(ws, signer, "1867542890123456789", {
  symbol: "ETH-USDT",
  side: "sell",
  orderType: "market",
  price: "",
  triggerPrice: "",
  quantity: "1.00",
  reduceOnly: false,
  postOnly: false,
  isTriggerMarket: false,
  clientOrderId: "0x" + crypto.randomBytes(16).toString('hex'),
  closePosition: false
});

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

:::note SubAccountAction Exception SubAccountAction endpoints (getPositions, getOpenOrders, getOrdersHistory, getTrades, getFundingPayments, getSubAccount, getDelegatedSigners, getBalanceUpdates) do not require a nonce. Only the signature and optional expiresAfter parameters are needed. :::

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