Webhooks
Register an HTTPS endpoint that the platform signs and posts to, and check it with a test delivery.
No events are delivered yet
You can create, list, update, delete and test webhooks. The API accepts eight event types when you register one, but no part of the platform emits any of them yet. The only request your endpoint receives is the test delivery you send with POST /api/v1/webhooks/{id}/test. Delivery retries are not running either.
Event Types
A webhook subscribes to 1 to 10 of these event types. None is delivered today.
| Event type | Accepted when you register | Delivered today |
|---|---|---|
trade.closed | Yes | No |
position.opened | Yes | No |
position.closed | Yes | No |
order.created | Yes | No |
order.filled | Yes | No |
order.cancelled | Yes | No |
account.balance_changed | Yes | No |
risk.threshold_breached | Yes | No |
webhook.test | No (400 VALIDATION_ERROR) | Only from the test route |
Registering a Webhook
The URL must start with https://. You may pass your own secret (16 to 64 characters); otherwise the API generates a 64-character hex secret. Each user can have 10 webhooks; the eleventh answers 409 WEBHOOK_LIMIT_REACHED. The API key needs the webhooks:write scope.
Via the API
POST /api/v1/webhooks
Content-Type: application/json
X-API-Key: tp_live_your_api_key
{
"url": "https://your-server.com/webhooks/trading",
"eventTypes": ["trade.closed", "position.opened"]
}The API answers 201. This is data from a real create, with its 64-character hex secret replaced by a placeholder. Only this reply carries the secret.
{
"id": "39ebb88f-a6d7-40d4-9644-0babd31f2934",
"url": "https://your-server.com/webhooks/trading",
"eventTypes": ["trade.closed", "position.opened"],
"enabled": true,
"failureCount": 0,
"lastSuccessAt": null,
"lastFailureAt": null,
"createdAt": "2026-10-02T15:48:05.848Z",
"updatedAt": "2026-10-02T15:48:05.848Z",
"secret": "<64 hex characters unless you passed your own>"
}Via the Dashboard
- Go to Settings → Webhooks
- Click Add Webhook
- Enter your endpoint URL and tick the event types
- Click Create Webhook
- Copy the secret: it is shown once
Creating a webhook sends nothing to the URL. Send a test delivery to check your endpoint.
Testing Your Endpoint
POST /api/v1/webhooks/39ebb88f-a6d7-40d4-9644-0babd31f2934/test
X-API-Key: tp_live_your_api_keyThe API posts one webhook.test delivery to the URL, waits up to 30 seconds and answers with the result:
{
"success": true,
"responseCode": 200,
"responseTimeMs": 1
}Any 2xx status is a success. A successful test sets the webhook's lastSuccessAt and resets its failureCount to 0; a failed test changes neither. Test deliveries are not written to the delivery log. The TypeScript SDK sends this request with client.webhooks.test(id); the Python SDK has no test method.
What Your Endpoint Receives
The test delivery
The API sets these headers on a test delivery. The values and the body are from a real one:
Content-Type: application/json
X-Webhook-Signature: sha256=1b93da8cfc2676099e0e49b05189d9d6aa31812b21fb3d00a797ebb012c2a431
X-Webhook-Id: 86dd5cb7-9e50-4725-a783-9a81102d7545
X-Webhook-Event: webhook.test
User-Agent: TradingPlatform-Webhook/1.0
{
"event": "webhook.test",
"data": {
"message": "This is a test webhook delivery",
"timestamp": "2026-10-02T15:56:12.971Z"
},
"timestamp": "2026-10-02T15:56:12.971Z",
"webhook_id": "86dd5cb7-9e50-4725-a783-9a81102d7545"
}Event deliveries (defined, not sent yet)
The delivery code sends every event in the same envelope: event names the event type, data holds the payload, timestamp is the send time and webhook_id names your webhook. There is no event id in the body. An event delivery adds two headers the test delivery does not carry: X-Webhook-Timestamp (the same value as timestamp) and X-Webhook-Delivery-Id.
This body came from the delivery code in an in-process run, called directly because nothing in the platform calls it:
{
"event": "order.filled",
"data": {
"order_id": "ord-1",
"account_id": "acc-1",
"symbol": "EURUSD",
"type": "buy",
"order_type": "market",
"volume": 0.1,
"fill_price": 1.1002,
"filled_volume": 0.1,
"fees": 0,
"position_id": "pos-1",
"filled_at": "2026-10-02T12:00:00.000Z"
},
"timestamp": "2026-10-02T15:48:06.654Z",
"webhook_id": "39ebb88f-a6d7-40d4-9644-0babd31f2934"
}The data fields defined for each event type. Fields are snake_case, type is the direction (buy or sell), and an optional field with no value is left out.
| Event type | Fields |
|---|---|
trade.closed | trade_id, position_id, account_id, symbol, type, volume, entry_price, exit_price, profit, profit_percentage, swap, commission, holding_time_seconds, opened_at, closed_at |
position.opened | position_id, account_id, symbol, type, volume, entry_price, margin_used, opened_at; optional stop_loss, take_profit |
position.closed | position_id, account_id, symbol, type, closed_volume, remaining_volume, entry_price, exit_price, realized_pnl, is_partial, closed_at; optional exit_reason |
order.created | order_id, account_id, symbol, type, order_type, volume, created_at; optional price, stop_loss, take_profit, client_id |
order.filled | order_id, account_id, symbol, type, order_type, volume, fill_price, filled_volume, filled_at; optional requested_price, fees, position_id |
order.cancelled | order_id, account_id, symbol, type, order_type, volume, cancelled_at; optional cancel_reason |
account.balance_changed | account_id, previous_balance, new_balance, change_amount, change_percentage, equity, margin, free_margin, change_reason, changed_at; optional margin_level |
risk.threshold_breached | account_id, threshold_type, threshold_value, current_value, severity, message, breached_at; optional affected_positions |
Verifying Webhook Signatures
X-Webhook-Signature is sha256= followed by the hex HMAC-SHA256 of the raw request body, keyed with your webhook's secret. Compute it over the bytes you received, before parsing the JSON. Test and event deliveries are signed the same way.
TypeScript Example
import crypto from "node:crypto";
function verifySignature(
payload: Buffer,
signature: string,
secret: string,
): boolean {
const expected = `sha256=${crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex")}`;
const given = Buffer.from(signature);
// timingSafeEqual throws on buffers of different lengths
return (
given.length === expected.length &&
crypto.timingSafeEqual(given, Buffer.from(expected))
);
}Python Example
import hashlib
import hmac
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)Managing Webhooks
Reads need the webhooks:read scope; changes need webhooks:write.
| Route | What it does |
|---|---|
GET /api/v1/webhooks | Your webhooks, without their secrets |
GET /api/v1/webhooks/{id} | One webhook |
PUT /api/v1/webhooks/{id} | Change url, eventTypes or enabled |
DELETE /api/v1/webhooks/{id} | Delete a webhook |
POST /api/v1/webhooks/{id}/test | Send a test delivery |
GET /api/v1/webhooks/{id}/deliveries | The delivery log, newest first: page, limit (1 to 100, default 20), status (success, failed or pending). Empty today. |
POST /api/v1/webhooks/{id}/deliveries/{deliveryId}/replay | Send one logged delivery again as a new delivery |
Both SDKs replay one delivery with client.webhooks.replay(webhookId, deliveryId). A replay is a new delivery with its own id, and the SDKs send it once and never resend it: if the answer is lost, read the delivery log before you replay again.
Retry Policy
The delivery code schedules a retry for a failed event delivery 1, 5, 15 and 60 minutes after attempts 1 to 4, and stops after attempt 5. After 5 failed attempts in a row it disables the webhook. None of this runs today: no job calls the retry processor, and no event is delivered in the first place. A failed test delivery does not count as a failure.
Best Practices
- • Verify the signature before you act on a delivery
- • Answer within 30 seconds, the API's timeout; do slow work after you answer
- • Keep the secret: only the create reply returns it
Next Steps
- API Quick Start - Authentication setup
- API Endpoints - Complete reference
- Code Examples - Integration samples
Risk Disclosure
Trading involves substantial risk of loss. Ensure your webhook handlers are reliable and properly tested. Never trade money you cannot afford to lose.