Webhooks
Get a signed HTTPS POST when an order fills, a position opens or closes, an account changes state, or a multi-account order finishes.
Instead of polling, give fxapis a URL and it tells you when something happens.
Each event is a signed POST, sent within a few seconds and retried for about
two days if your server is down.
Webhooks are on every paid plan. Manage them in the console under Webhooks,
or with the API (/v1/webhooks, scope webhooks:write).
Add an endpoint
curl https://api.fxapis.com/v1/webhooks \
-H "Authorization: Bearer $FXAPIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/fxapis/webhooks", "events": ["order.filled", "position.closed"]}'The response carries secret (whsec_…). It is shown once — store it with
your other secrets. Leave out events to receive every event. Up to 10 endpoints
per workspace; each URL must be HTTPS on port 443 and reachable from the internet.
Send yourself a test event to check the wiring:
curl -X POST https://api.fxapis.com/v1/webhooks/$ENDPOINT_ID/test \
-H "Authorization: Bearer $FXAPIS_API_KEY"What arrives
POST /fxapis/webhooks HTTP/1.1
Content-Type: application/json
fxapis-event-id: 5b0e7c1a-2f7d-4c1e-9a53-0e6f3b1c9a10
fxapis-signature: t=1791400000,v1=5f2b…
{
"id": "5b0e7c1a-2f7d-4c1e-9a53-0e6f3b1c9a10",
"type": "order.filled",
"createdAt": "2026-10-08T12:00:00.000Z",
"data": { "id": "…", "accountId": "…", "symbol": "EURUSD", "state": "filled", "previousState": "sending", "…": "…" }
}data is the resource exactly as the REST API returns it — the order from
GET /v1/orders/{id}, the position from the positions list, the account from
GET /v1/accounts/{id}, the multi-account order from
GET /v1/execution-waves/{id} — read when the event is sent, normally within
two seconds of the change.
| Event | When | data |
|---|---|---|
order.filled | An order filled, fully or partly — from the broker's reply or found later by reconciliation | The order, with previousState |
order.rejected | The broker refused an order | The order |
order.unresolved | An order was sent and no answer came back; reconciliation takes over | The order |
order.resolved | An unresolved order's outcome was found (an order.filled follows if it filled) | The order |
order.cancelled | A pending order was withdrawn | The order |
position.opened | A position appeared on the account | The position |
position.closed | A position is gone from the account, whatever closed it | The position |
account.state_changed | The account became ready, offline or standby, or needs you (invalid_credentials, needs_2fa, needs_certificate, trading_disabled, error) | The account, with previousState |
wave.settled | A multi-account order finished: settled, cancelled or abandoned | The multi-account order with its legs |
webhook.test | You asked for a test | { endpointId, message } |
Positions come from the account's position sync, which runs about every 15
seconds while the account is online — so position.opened and
position.closed arrive up to about 15 seconds after the broker changed them.
A position that was already open when the account connected is reported as
opened the first time it is seen.
Verify the signature
Check every delivery before trusting it. fxapis-signature is
t=<unix seconds>,v1=<hex HMAC-SHA256 of "t.body" with your secret>. Use the
raw body — the bytes as they arrived, before JSON parsing — and reject anything
more than 5 minutes old.
Node / TypeScript (npm install fxapis):
import { verifyWebhook } from "fxapis";
app.post("/fxapis/webhooks", express.raw({ type: "application/json" }), async (req, res) => {
let event;
try {
event = await verifyWebhook(req.body.toString("utf8"), req.header("fxapis-signature"), process.env.FXAPIS_WEBHOOK_SECRET!);
} catch {
return res.sendStatus(400);
}
if (await alreadyHandled(event.id)) return res.sendStatus(200);
await handle(event);
res.sendStatus(200);
});Python (pip install fxapis):
from fxapis import WebhookVerificationError, webhooks
@app.post("/fxapis/webhooks")
def receive(request):
try:
event = webhooks.verify(request.body, request.headers.get("fxapis-signature"), FXAPIS_WEBHOOK_SECRET)
except WebhookVerificationError:
return Response(status=400)
if already_handled(event["id"]):
return Response(status=200)
handle(event)
return Response(status=200)Any language: split the header on ,; take t and every v1; compute
HMAC-SHA256(secret, t + "." + rawBody) as lowercase hex; accept if it equals
any v1 (compare in constant time) and t is within 300 seconds of now.
Answer quickly, and expect repeats
- Answer
2xxwithin 10 seconds. Do slow work after answering. Redirects are not followed — a3xxcounts as a failure. - Delivery is at least once: the same event can arrive twice (a retry after a
timeout, or a redelivery you asked for). Deduplicate on
id. - Events for different resources can arrive out of order. Use
createdAtand the resource's own state, not arrival order.
Retries and switching off
A delivery that does not get a 2xx is retried after 1 minute, 5 minutes,
30 minutes, 2, 6, 12 and 24 hours — eight attempts over about 45 hours — then
given up. Each endpoint retries on its own: one failing endpoint never delays
another.
After 20 failed deliveries in a row the endpoint is switched off and the
workspace owner gets an email. Fix it and resume it in the console (or
POST /v1/webhooks/{id}/enable). Events already queued to it before it was
switched off can be redelivered from its delivery log
(POST /v1/webhooks/{id}/deliveries/{eventId}/redeliver); events recorded
while it was off never reached that log and can't be redelivered that way.
Events and delivery logs are kept for 30 days.
Rotate the secret
POST /v1/webhooks/{id}/rotate returns a new secret, once. For the next 24 hours
every delivery is signed with both secrets — two v1= values — so you can
deploy the new secret and then retire the old one without missing an event.
Idempotent MT5 orders: never double-fill on retry
Why retrying a MetaTrader 5 order can open a second position, and how the fxapis Idempotency-Key prevents it: 24-hour keys, replays, in-flight and reuse conflicts, the unknown outcome, and key patterns for signals, copiers and queues.
TradingView alerts to MT5 — no EA, no VPS
Turn TradingView alerts into MetaTrader 5 orders with a secret webhook URL: create a hook in the console or the API, set up the TradingView alert step by step, message formats and placeholders for indicators and strategies, closing, idempotency, the IP allowlist, troubleshooting and limits.