On Trader, the Trade Architect can send your plan to a receiver you run (an MT4/MT5 Expert Advisor, a cTrader cBot, your own script) as a broker-side pending order. This page is for whoever writes that receiver.
POST to your receiver URL, Content-Type: application/json. Headers you use:
| Header | Meaning |
|---|---|
X-ForexFin-Signature | t=<unix seconds>,v1=<hex>: HMAC-SHA256 with your signing secret over <t>.<raw body>. |
X-ForexFin-Event | order.plan or order.test. |
X-ForexFin-Idempotency-Key | Same as the body's idempotency_key. |
Delivery runs through AgentDelivery, which retries on timeouts and non-2xx answers, so the same payload can arrive more than once. Other headers from the delivery service may be present; ignore them. Answer 2xx once you have stored or acted on the payload.
forexfin.order.v2){
"schema": "forexfin.order.v2",
"event": "order.plan",
"id": "ffo_Xr3…", // ForexFin order id
"idempotency_key": "u14:plan:8f3a…", // dedupe on this
"created_at": "2026-10-05T09:12:00.000Z",
"valid_until": "2026-10-05T17:12:00.000Z",// refuse after this; also use as the order's expiry
"account_label": "FTMO 100k", // or null
"symbol": "EURUSD", // canonical
"broker_symbol": "EURUSD.pro", // your platform's name when your journal knows it, else null
"side": "buy", // buy | sell
"order_type": "buy_limit", // buy_limit | sell_limit | buy_stop | sell_stop
"entry": 1.0845,
"stop_loss": 1.0815,
"take_profit": 1.0905, // or null
"volume_lots": 0.33, // standard lots (100,000 units), rounded down
"units": 33000,
"guard": {
"reference_price": 1.0845, // = entry
"max_deviation_pips": 2,
"max_deviation_points": 20, // 5-digit / 3-digit quotes
"refuse_if_triggered": true
},
"risk": { "balance": 10000, "account_ccy": "USD", "risk_pct": 1, "risk_amount": 99, "stop_pips": 30 },
"rules_checked": ["session_window", "max_correlated_open", "tilt_guard"],
"source": { "app": "trade-architect", "sizing_rates": "ECB reference rates" },
"note": "your note, or null"
}
order.test carries only schema, event, id, idempotency_key, created_at and note. Verify the signature, answer 2xx, place nothing.
Sizing uses ECB reference rates for the currency conversion (exact when the account currency is the pair's base or quote). Check volume_lots against your broker's minimum and step before placing.
t more than 5 minutes from your clock.idempotency_key, answer 2xx and do nothing.valid_until.refuse_if_triggered: true, refuse. With false, you may fill at market only if the price is within max_deviation_pips of the entry; otherwise refuse.valid_until.| Payload | MT5 (MqlTradeRequest) | cTrader |
|---|---|---|
buy_limit / sell_limit | ORDER_TYPE_BUY_LIMIT / ORDER_TYPE_SELL_LIMIT | PlaceLimitOrder |
buy_stop / sell_stop | ORDER_TYPE_BUY_STOP / ORDER_TYPE_SELL_STOP | PlaceStopOrder |
valid_until | type_time = ORDER_TIME_SPECIFIED, expiration | expiryTime |
guard.max_deviation_points | deviation (market fill only) | slippage in pips (market fill only) |
MT4/MT5 can't receive HTTP directly. The usual setup is a small HTTPS service that stores payloads and an EA that polls it with WebRequest; that service is where the checks above run.
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t, sig = parts.get("t", ""), parts.get("v1", "")
if not t.isdigit() or abs(time.time() - int(t)) > tolerance:
return False
mac = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(mac, sig)
const crypto = require("crypto");
function verify(rawBody, header, secret, tolerance = 300) {
const parts = Object.fromEntries(String(header).split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > tolerance) return false;
const mac = crypto.createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest("hex");
const a = Buffer.from(mac), b = Buffer.from(String(parts.v1 || ""));
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
forexfin.alert.v2)Price alerts set to webhook delivery use the same envelope, headers and signature. X-ForexFin-Event is alert.triggered. Each alert fires once, so the idempotency key is per alert. The signing secret is per account, separate from the order receiver's; it is shown once when you create your first webhook alert, and Alerts → Delivery → new signing secret replaces it.
{
"schema": "forexfin.alert.v2",
"event": "alert.triggered",
"id": "ffa_123",
"idempotency_key": "u14:alert:123",
"created_at": "2026-10-05T16:03:01.000Z",
"symbol": "EURUSD",
"broker_symbol": null,
"asset_class": "forex",
"direction": "above",
"threshold": 1.12,
"trigger_price": 1.1201,
"current_price": 1.1203,
"triggered_at": "2026-10-05T16:03:01.000Z",
"note": null
}
Prices are the latest quote at the 60-second check, not a tick feed.
Terms for this feature: Terms of Use, section 4. Data handling: Privacy Policy.