Webhook Trading
Send orders from a script, trading bot, or TradingView alert using CrossTrade's webhook command system. Your requests use the same command validation, rate limiting, trading windows, and other supported webhook options as any external alert.
The same webhook URL accepts two message formats:
| Format | Content-Type | Body |
|---|---|---|
| JSON | application/json | One flat object using CrossTrade field names |
| Semicolon text | text/plain | key=value; fields |
JSON is also accepted when sent as text/plain. Headers with a charset, such as application/json; charset=utf-8, are supported. Existing semicolon alerts continue to work.
Endpoint and authentication
Send an HTTP POST to the personal Webhook URL shown on your My Account page:
https://app.crosstrade.io/v1/send/{uid}/{channel}
Copy the complete URL from your account. Include your Secret Key in the body as "key": "YOUR_SECRET_KEY" for JSON or key=YOUR_SECRET_KEY; for semicolon text. No Authorization header is required. For JSON, a Bearer header does not replace the body's key field.
Webhook JSON uses the webhook field names, such as qty, order_type, and stop_loss. The separate REST API uses /v1/api/... routes, Bearer authentication, and its own request schemas. For example, a REST orderType field is not interchangeable with the webhook field order_type.
JSON alert messages
NinjaTrader market order
This sends a one-contract buy market order to Sim101 through the CrossTrade NT8 Add-On:
{
"key": "YOUR_SECRET_KEY",
"command": "place",
"account": "Sim101",
"instrument": "MES 12-26",
"action": "buy",
"qty": 1,
"order_type": "market",
"tif": "day",
"destination": "nt8"
}
NinjaTrader is the default destination if destination is omitted. NinjaTrader and the CrossTrade NT8 Add-On must be running and connected.
The equivalent semicolon message is:
key=YOUR_SECRET_KEY;
command=place;
account=Sim101;
instrument=MES 12-26;
action=buy;
qty=1;
order_type=market;
tif=day;
destination=nt8;
Tradovate entry with a target and stop
Set destination to tradovate and supply a linked Tradovate account. Keep relative prices with units as strings:
{
"key": "YOUR_SECRET_KEY",
"command": "place",
"account": "YOUR_DEMO_ACCOUNT",
"instrument": "MES1!",
"action": "buy",
"qty": 1,
"order_type": "market",
"tif": "day",
"destination": "tradovate",
"take_profit": "40 ticks",
"stop_loss": "20 ticks",
"require_market_position": "flat"
}
For this market buy, CrossTrade calculates the target 40 ticks above the live reference quote and the stop 20 ticks below it, then submits absolute prices. They are not recalculated from the actual fill. See relative-price references for details. require_market_position blocks the entry unless the account is flat on that instrument. Direct Tradovate execution requires a linked account, but no NinjaTrader installation or desktop add-on.
JSON changes how you write the message, not which features each broker supports. See Destinations for the capability matrix and broker-specific fields.
Field names and values
Use the fields documented under Commands and Advanced Options. Required fields and their meanings are the same in either format.
| Rule | Example or explanation |
|---|---|
| One non-empty, flat object | No batch arrays or nested order objects. |
| Case-insensitive field names | qty and QTY refer to the same field. Preserve the documented spelling and underscores. |
| Strings, finite numbers, or booleans | "qty": 1, "stop_loss": "20 ticks", "sync_strategy": true. Each field still has its own validation. |
| String identifiers | Keep secrets, account names, instruments, and order IDs as strings. This preserves leading zeros. |
| Supported aliases | quantity maps to qty; atm_strategy maps to strategy. |
| No duplicate fields | qty with QTY, or qty with quantity, is rejected. |
| No nulls or empty values | Omit an unused optional field instead of sending null or "". |
| No unknown fields | Use CrossTrade's webhook vocabulary. |
| No delimiters or control characters in values | Values cannot contain ;, =, or embedded control characters, including escaped newlines or tabs. |
Leading and trailing whitespace in field names and string values is trimmed. Pretty-printed JSON with line breaks between fields is supported. Standard JSON syntax applies: double-quoted keys and strings, no comments, and no trailing commas.
For fields that already accept comma-separated lists, keep the list in a string. For example, "account": "Sim101,Sim102" uses the existing multi-account syntax; "account": ["Sim101", "Sim102"] is rejected.
CrossTrade normalizes valid JSON into its semicolon command representation internally. Alert History shows that normalized command, so seeing KEY=...;COMMAND=...; after sending JSON is expected.
CrossTrade accepts its own flat JSON schema. Payloads from PickMyTrade or TradersPost may use different names or nested objects, such as ticker or stopLoss. Use the Command Builder's JSON converter and review conversion warnings before sending them. A valid CrossTrade JSON object does not need conversion.
Sending requests
Replace the webhook URL, secret, account, and instrument in each example with your own values. Test with a simulation or demo account first.
cURL
curl --request POST 'YOUR_CROSSTRADE_WEBHOOK_URL' \
--header 'Content-Type: application/json' \
--data-raw '{
"key": "YOUR_SECRET_KEY",
"command": "place",
"account": "Sim101",
"instrument": "MES 12-26",
"action": "buy",
"qty": 1,
"order_type": "market",
"tif": "day",
"destination": "nt8"
}'
Code Example (Python)
This example sends a semicolon command as plain text:
import requests
# Webhook URL
url = "https://app.crosstrade.io/v1/send/abc123/abcdefghijklmnopqrstuvwxyz"
# This example sends a semicolon command as plain text
headers = {
"Content-Type": "text/plain"
}
# Message payload text
data = '''
key=my-secret-key;
command=PLACE;
account=Sim105;
instrument=MES 12-26;
action=BUY;
qty=1;
tif=DAY;
order_type=MARKET;
'''
# Post it to CrossTrade!
try:
response = requests.post(url, headers=headers, data=data)
print(f"Response Code: {response.status_code}, Response Text: {response.text}")
except Exception as e:
print(f"An error occurred: {e}")
JSON example (Python)
Use json=payload with requests. It serializes the object and sets Content-Type: application/json automatically.
import requests
url = "YOUR_CROSSTRADE_WEBHOOK_URL"
payload = {
"key": "YOUR_SECRET_KEY",
"command": "place",
"account": "Sim101",
"instrument": "MES 12-26",
"action": "buy",
"qty": 1,
"order_type": "market",
"tif": "day",
"destination": "nt8",
}
response = requests.post(url, json=payload, timeout=15)
print(response.status_code, response.text)
For a semicolon message, use data=message with headers={"Content-Type": "text/plain"} instead. Passing a Python dictionary as data=payload sends form-encoded data, which this webhook endpoint does not accept.
JavaScript (Node.js)
const payload = {
key: 'YOUR_SECRET_KEY',
command: 'place',
account: 'Sim101',
instrument: 'MES 12-26',
action: 'buy',
qty: 1,
order_type: 'market',
tif: 'day',
destination: 'nt8',
};
const response = await fetch('YOUR_CROSSTRADE_WEBHOOK_URL', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(15000),
});
console.log(response.status, await response.text());
Using JSON in TradingView
Paste the entire JSON object into the alert's Message field and use your existing CrossTrade Webhook URL in Notifications. Do not wrap the object in another string or include Markdown code fences. TradingView chooses the request Content-Type automatically; CrossTrade accepts JSON with either application/json or text/plain.
Strategy placeholders can be used as string values:
{
"key": "YOUR_SECRET_KEY",
"command": "place",
"account": "Sim101",
"instrument": "MES1!",
"action": "{{strategy.order.action}}",
"qty": "{{strategy.order.contracts}}",
"order_type": "market",
"tif": "day"
}
TradingView substitutes these values when the strategy alert fires. Numeric fields such as qty can be supplied as strings, so the placeholders can remain quoted. They do not resolve when sent from cURL or your own script. See TradingView Alerts for alert setup.
Responses and troubleshooting
Read the response body as well as the HTTP status. A success response is not a broker fill confirmation. TradingView requests are acknowledged before background processing finishes; use Alert History to check processing results and broker orders to verify execution.
| Problem | What to check |
|---|---|
| Invalid request format at the HTTP boundary | Use POST with application/json or text/plain, not form encoding. |
| Invalid JSON or duplicate field | Check commas, double quotes, and repeated keys, including case variants and aliases. |
| Unsupported JSON command field | Use webhook names such as order_type, not REST names such as orderType or another platform's schema. |
| Invalid JSON value | Remove nulls, nested objects, arrays, empty values, and forbidden characters. |
| Missing, invalid, or inactive secret | Include the correct Secret Key inside the JSON object's key field. |
A timeout does not establish whether an order was placed. Check Alert History and broker orders before retrying an order-changing request. Changing the message format does not add an exactly-once delivery guarantee.