Place Orders (WebSocket)
Place orders through the WebSocket connection for lowest latency trading.
Endpoint
ws.send() wss://api.synthetix.io/v1/ws/tradeRequest
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
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Client-generated unique request identifier |
method | string | Yes | Must be "post" |
params | object | Yes | Contains all parameters for the request |
Params Object
| Parameter | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Must be "placeOrders" |
orders | array | Yes | Array of order objects (minimum 1 order). Each order uses static fields: orderType, price, triggerPrice, isTriggerMarket |
grouping | string | No | Order grouping: "na", "normalTpsl", "positionTpsl" |
subAccountId | string | Yes | Subaccount identifier |
nonce | integer | Yes | Incrementing nonce (Unix ms timestamp as number) |
expiresAfter | integer | No | Expiration timestamp (Unix ms as number) |
signature | object | Yes | EIP-712 signature |
Order Object
| Parameter | Type | Required | Description |
|---|---|---|---|
symbol | string | Yes | Market symbol (e.g., "BTC-USDT") |
side | string | Yes | "buy" or "sell" |
orderType | string | Yes | One of: limitGtc, limitIoc, limitAlo, market, triggerSl, triggerTp |
price | string | Yes | Price as string. Use empty string when not applicable (market orders and trigger market) |
quantity | string | Yes | Order size as string |
reduceOnly | boolean | Yes | Only reduce position |
triggerPrice | string | Yes | Trigger price as string. Required for triggerSl and triggerTp; empty string otherwise |
isTriggerMarket | boolean | Yes | Execution type for trigger orders. true for market-on-trigger, false for limit-on-trigger. Must be false for non-trigger types |
clientOrderId | string | No | Client-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:triggerPricerequired;isTriggerMarketdetermines execution; iftruethenprice="", iffalsethenpricerequiredtriggerTp: same rules astriggerSl
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
| Error | Description |
|---|---|
| Invalid signature | EIP-712 signature validation failed |
| Invalid market symbol | Market symbol not recognized |
| Nonce already used | Nonce must be greater than previous value |
| Rate limit exceeded | Too many requests in time window |
| Request expired | expiresAfter timestamp has passed |
Next Steps
- WebSocket Authentication - Auth setup
- REST Place Orders - REST alternative
- Cancel Orders - Order cancellation