Webhooks · Overview
Webhooks carry the state changes that matter — a trader joining, a signal going live, an execution succeeding or failing. One endpoint, one signing secret, clear event names, and a dashboard that tells you exactly what happened on every delivery.
Five minutes
If you already understand the shape of a POST webhook, this is the core contract: read the raw body, verify, enqueue. GET endpoints use the same headers but verify the canonical query string instead of a body.
// app/api/webhooks/trading-layer/route.ts
import { NextResponse } from "next/server";
import { verifyTradingLayerWebhook } from "~/server/webhooks/verify";
import { enqueueDownstream } from "~/server/webhooks/queue";
export async function POST(req: Request) {
// IMPORTANT: read the raw body before any .json() call.
const rawBody = await req.text();
try {
verifyTradingLayerWebhook({
rawBody,
signatureHeader: req.headers.get("X-Webhook-Signature"),
secret: process.env.TRADING_LAYER_SIGNING_SECRET!,
});
} catch (err) {
return new NextResponse("invalid signature", { status: 401 });
}
const event = JSON.parse(rawBody);
// Idempotency — we may retry. Dedupe on the event id.
await enqueueDownstream(event.id, event);
return NextResponse.json({ received: true });
}express.json(), for example) will discard whitespace and key order — verification will then fail on a valid payload. Always route POST webhook endpoints through a raw-body path.From zero to delivering
Each endpoint has its own signing secret, subscription list, referral-link filter, and independent delivery log. Most teams run one production endpoint and a second one pointed at a staging receiver.
Anatomy of a delivery
Every delivery is signed and includes the same routing headers. POST sends JSON; GET sends the configured params as query parameters.
application/json. GET deliveries send configured params in the query string and have no body.t=<unix>,v1=<hex_hmac>. The timestamp and the HMAC-SHA256 signature, comma-separated. See signature verification.Subscribe selectively
Event names are stable and intended to be part of your integration contract. You can toggle groups independently from the dashboard; adding new events to a group never changes the names of existing ones.
Onboarding and connection state for traders in your bot.
trader.startedtrader.onboardedtrader.mt5_connectedtrader.mt5_failedtrader.mt5_disconnectedEverything that happens to a signal and its per-trader actions.
signal.publishedsignal.invalidatedsignal.action.viewedsignal.action.executedsignal.action.failedNeed the full reference?
Per-event descriptions, supported dynamic fields, and referral-filter flags live on a dedicated page.
What lands at your door
Every endpoint sends the params you configure. Existing endpoints without custom params keep the default envelope fields: id, type, createdAt, and data.
{
"event": "trader.started",
"telegram_id": 1880622198,
"referral_link_id": "550e8400-e29b-41d4-a716-446655440000",
"click_id": "JOPAPETUHA"
}When things go sideways
We retry transient failures automatically and keep the full delivery history — attempt counts, response codes, bodies, and the last error — alongside every webhook.
2xx
Any 2xx is a success. The delivery log retains the body and status for inspection.
4xx / 5xx
Non-2xx and network errors are retried with backoff. We stop at the terminal threshold and mark the delivery as failed.
Manual
Failed or dead deliveries can be replayed from the dashboard — the original payload and headers are preserved exactly.
X-Webhook-Event-Id and let us do the retrying.