Documentation

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 typeAccepted when you registerDelivered today
trade.closedYesNo
position.openedYesNo
position.closedYesNo
order.createdYesNo
order.filledYesNo
order.cancelledYesNo
account.balance_changedYesNo
risk.threshold_breachedYesNo
webhook.testNo (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

  1. Go to Settings → Webhooks
  2. Click Add Webhook
  3. Enter your endpoint URL and tick the event types
  4. Click Create Webhook
  5. 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_key

The 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 typeFields
trade.closedtrade_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.openedposition_id, account_id, symbol, type, volume, entry_price, margin_used, opened_at; optional stop_loss, take_profit
position.closedposition_id, account_id, symbol, type, closed_volume, remaining_volume, entry_price, exit_price, realized_pnl, is_partial, closed_at; optional exit_reason
order.createdorder_id, account_id, symbol, type, order_type, volume, created_at; optional price, stop_loss, take_profit, client_id
order.filledorder_id, account_id, symbol, type, order_type, volume, fill_price, filled_volume, filled_at; optional requested_price, fees, position_id
order.cancelledorder_id, account_id, symbol, type, order_type, volume, cancelled_at; optional cancel_reason
account.balance_changedaccount_id, previous_balance, new_balance, change_amount, change_percentage, equity, margin, free_margin, change_reason, changed_at; optional margin_level
risk.threshold_breachedaccount_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.

RouteWhat it does
GET /api/v1/webhooksYour 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}/testSend a test delivery
GET /api/v1/webhooks/{id}/deliveriesThe 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}/replaySend 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

⚠️

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.