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",
    "source": "direct",
    "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"
sourcestringNoRequest source identifier for tracking and rebates (max 100 characters). Use "direct" for generic integrations
subAccountIdstringYesSubaccount identifier
nonceintegerYesPositive integer, incrementing nonce
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": {
          "order": {
            "venueId": "1948058938469519360",
            "clientId": "cli-1948058938469519360"
          },
          "id": "1948058938469519360"
        }
      }
    ]
  }
}

Filled Order (Immediately Executed)

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

Order Error

{
  "id": "place-orders-1",
  "status": 200,
  "result": {
    "statuses": [
      {
        "error": "Insufficient margin",
        "errorCode": "INSUFFICIENT_MARGIN",
        "order": { "venueId": "0", "clientId": "cli-1948058938469519360" }
      }
    ]
  }
}
Response Fields:
FieldTypeDescription
resting.order.venueIdstringCanonical order ID for accepted orders
resting.idstringDeprecated order ID for accepted orders
filled.order.venueIdstringCanonical order ID for filled orders
filled.idstringDeprecated order ID for filled orders
filled.totalSizestringTotal filled quantity
filled.avgPricestringAverage execution price
errorstringError message if order failed
errorCodestringMachine-readable error code 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
  • Use *.order.venueId as canonical; id remains deprecated compatibility output

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 strictly 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 any positive integer as nonce
  • Each nonce must be greater than the previous one (incrementing)
  • Date.now() is a convenient option, not a requirement
  • If nonce conflicts occur, increment by 1 and retry

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

Error CodeDescriptionRetryable
UNAUTHORIZEDEIP-712 signature validation failedNo
VALIDATION_ERRORRequest validation failedNo
MISSING_REQUIRED_FIELDRequired field is missingNo
INVALID_FORMATField format is invalidNo
INVALID_VALUEInvalid parameter valueNo
RATE_LIMIT_EXCEEDEDToo many requests in time windowYes
INSUFFICIENT_MARGINNot enough margin for tradeNo
ORDER_NOT_FOUNDOrder does not existNo
OPERATION_TIMEOUTOperation timed outYes

Next Steps