Skip to content

Webhooks ​

Trade It can notify you via webhook when events like trade fills and account connections happen. Up to 5 endpoints per organization can be configured at Organizations → Webhooks.

Events cover your organization's active members, and each endpoint only receives the event types it subscribes to.

Partner portal Webhooks tab with one endpoint, its event log, and the payload and delivery attempt of an ACCOUNT event
An endpoint, its event log, and the payload and attempts of a delivered event.

Payload ​

Every event is a JSON POST with the same envelope:

json
{
  "id": "0a67c0b8-57ae-4cdf-ab8b-5b249e83c100",
  "type": "TRADE_UPDATED",
  "created_at": "2026-10-07T14:31:12.000Z",
  "data": {}
}
FieldDescription
idUnique event ID, also sent as the TradeIt-Event-Id header. Retries reuse it, so deduplicate on it.
typeOne of the event types below.
created_atWhen the event happened, as an ISO 8601 timestamp.
dataSnapshot of the Account or Trade the event is about, taken when it happened. Private brokerage identifiers like external_id are left out.

Event types ​

ACCOUNT_ADDED ​

Sent when a brokerage account is linked. Connecting a brokerage sends one for each of its accounts, and a later sync sends one for any new account the brokerage reports.

Data: Account

json
{
  "id": "5f0c7e7a-3b0e-4a43-9a5e-1c2f7d8e9b10",
  "type": "ACCOUNT_ADDED",
  "created_at": "2026-10-07T14:30:00.000Z",
  "data": {
    "id": 304,
    "connection_id": 395,
    "name": "Robinhood x1234",
    "balance": { "amount": 15420.5, "currency": "USD" },
    "cash": { "amount": 3420.5, "currency": "USD" },
    "buying_power": { "amount": 3420.5, "currency": "USD" },
    "synced_at": "2026-10-07T14:30:00.000Z",
    "holdings_synced_at": null,
    "trades_synced_at": null,
    "created_at": "2026-10-07T14:30:00.000Z",
    "updated_at": "2026-10-07T14:30:00.000Z"
  }
}

ACCOUNT_DELETED ​

Sent when an account is removed, either because the user deleted its brokerage connection or because the brokerage removed the account from the connection.

Data: Account as it was just before removal

Note

The account's id stops working in API requests.

json
{
  "id": "c3d1a9e2-7f4b-4c8e-b1a6-2e9f0d7c5b34",
  "type": "ACCOUNT_DELETED",
  "created_at": "2026-10-09T18:02:44.000Z",
  "data": {
    "id": 304,
    "connection_id": 395,
    "name": "Robinhood x1234",
    "balance": { "amount": 15873.12, "currency": "USD" },
    "cash": { "amount": 2412.08, "currency": "USD" },
    "buying_power": { "amount": 2412.08, "currency": "USD" },
    "synced_at": "2026-10-09T17:45:10.000Z",
    "holdings_synced_at": "2026-10-09T17:45:12.000Z",
    "trades_synced_at": "2026-10-09T06:00:03.000Z",
    "created_at": "2026-10-07T14:30:00.000Z",
    "updated_at": "2026-10-09T17:45:12.000Z"
  }
}

ACCOUNT_DISCONNECTED ​

Sent when an account's brokerage connection breaks, usually because the brokerage revoked access or the login expired. Trades and syncs for the account fail until the user reconnects the brokerage in Trade It.

Data: Account

Note

Each account on the connection gets its own event, so a login with three accounts sends three.

json
{
  "id": "8b2e4f61-0d3a-4e7c-9f15-6a8c2b4d1e90",
  "type": "ACCOUNT_DISCONNECTED",
  "created_at": "2026-10-08T03:12:09.000Z",
  "data": {
    "id": 304,
    "connection_id": 395,
    "name": "Robinhood x1234",
    "balance": { "amount": 15873.12, "currency": "USD" },
    "cash": { "amount": 2412.08, "currency": "USD" },
    "buying_power": { "amount": 2412.08, "currency": "USD" },
    "synced_at": "2026-10-07T21:00:04.000Z",
    "holdings_synced_at": "2026-10-07T21:00:06.000Z",
    "trades_synced_at": "2026-10-07T06:00:03.000Z",
    "created_at": "2026-10-07T14:30:00.000Z",
    "updated_at": "2026-10-07T21:00:06.000Z"
  }
}

ACCOUNT_RECONNECTED ​

Sent when the user reconnects a brokerage, whether or not the connection had broken. Each account on the connection gets its own event.

Data: Account

Note

Accounts the brokerage reports for the first time arrive as ACCOUNT_ADDED instead.

json
{
  "id": "e47a0c95-2b6d-4f13-8c7e-91d5a3f6b208",
  "type": "ACCOUNT_RECONNECTED",
  "created_at": "2026-10-08T15:20:31.000Z",
  "data": {
    "id": 304,
    "connection_id": 395,
    "name": "Robinhood x1234",
    "balance": { "amount": 15873.12, "currency": "USD" },
    "cash": { "amount": 2412.08, "currency": "USD" },
    "buying_power": { "amount": 2412.08, "currency": "USD" },
    "synced_at": "2026-10-07T21:00:04.000Z",
    "holdings_synced_at": "2026-10-07T21:00:06.000Z",
    "trades_synced_at": "2026-10-07T06:00:03.000Z",
    "created_at": "2026-10-07T14:30:00.000Z",
    "updated_at": "2026-10-07T21:00:06.000Z"
  }
}

TRADE_UPDATED ​

Sent when a trade reaches a final status: complete, canceled, failed, or disconnected, which means the brokerage connection broke before the order resolved.

Data: Trade without the nested asset; use asset_id to look it up

Note

Partial fills and other in-progress changes are not sent.

json
{
  "id": "0a67c0b8-57ae-4cdf-ab8b-5b249e83c100",
  "type": "TRADE_UPDATED",
  "created_at": "2026-10-07T14:31:12.000Z",
  "data": {
    "id": 842,
    "user_id": 1,
    "account_id": 304,
    "asset_id": 88,
    "source": "api",
    "type": 0,
    "action": "buy",
    "direction": null,
    "order_type": "limit",
    "time_in_force": "day",
    "status": "complete",
    "total_units": 4,
    "filled_units": 4,
    "canceled_units": 0,
    "limit_price": { "amount": 250, "currency": "USD" },
    "stop_price": null,
    "take_profit": null,
    "stop_loss": null,
    "notional_amount": null,
    "execution_price": { "amount": 248.12, "currency": "USD" },
    "fee": null,
    "legs": null,
    "event": null,
    "error_message": null,
    "analysis": null,
    "image_url": null,
    "placed_at": "2026-10-07T14:30:58.000Z",
    "executed_at": "2026-10-07T14:31:10.000Z",
    "created_at": "2026-10-07T14:30:41.000Z",
    "updated_at": "2026-10-07T14:31:12.000Z"
  }
}

Verify the signature ​

Requests include TradeIt-Signature: t=<unix-seconds>,v1=<hex-hmac> and TradeIt-Event-Id. Compute HMAC-SHA256 with your endpoint's complete whsec_… secret over ${timestamp}.${rawBody}. Compare signatures using a constant-time comparison and reject timestamps more than five minutes from your server's clock. Verify before parsing or acting on the event.

ts
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyWebhook(rawBody: Buffer, header: string, secret: string) {
  const parts = header.split(',');
  const timestampText = parts.find(part => part.startsWith('t='))?.slice(2) ?? '';
  const supplied = parts.find(part => part.startsWith('v1='))?.slice(3) ?? '';
  const timestamp = Number(timestampText);

  if (!/^\d+$/.test(timestampText) || !Number.isSafeInteger(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > 300 || !/^[a-f0-9]{64}$/.test(supplied)) throw new Error('Invalid webhook signature');

  const expected = createHmac('sha256', secret).update(`${timestampText}.`).update(rawBody).digest();
  if (!timingSafeEqual(expected, Buffer.from(supplied, 'hex'))) throw new Error('Invalid webhook signature');
  
  return JSON.parse(rawBody.toString('utf8'));
}

Note

Configure your framework to provide the original request bytes. Re-serializing parsed JSON changes the signed bytes. Keep your clock synchronized and never expose the signing secret in frontend code.

Delivery and retries ​

Persist the event to your own durable inbox, then return a 2xx response promptly. Requests time out after five seconds. Every failed HTTP response, redirect, timeout, or network error retries after 1 minute, 5 minutes, and 30 minutes, for four attempts total. Changes are queued as they are processed; delivery timing depends on queue backlog.

Note

Delivery is at least once and may arrive out of order. Deduplicate using event.id across retries, which keep the original event body and ID and receive a fresh signature timestamp.

Pausing or removing an endpoint stops its queued deliveries; requests already in flight may finish. Secret rotation immediately affects future attempts, so update your receiver promptly.

Note

Only public HTTPS hostnames are supported, and redirects are not followed.

Event logs ​

The portal's Events list keeps 30 days of events sent to your endpoints, filterable by status and event type. Select an event to inspect its payload and HTTP attempt history, including response codes, response excerpts, and durations.

Note

Removing an endpoint also removes its event history.