> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trdrs.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Events and webhooks

> Most of the partner API answers questions you ask.

Most of the partner API answers questions you ask. Webhooks are the other direction: trdrs calls
your back office when something happens, so you stop polling for it.

Register an endpoint, and we POST a signed JSON body to it every time one of your firm's events
fires. You can register up to five endpoints per firm, each with its own event filter, so a
billing system and an ops dashboard can subscribe to different things.

## What you receive

Every delivery is a POST with the same envelope:

```json theme={null}
{
  "type": "registration.linked",
  "createdAt": "2026-08-24T19:04:12.318Z",
  "data": {
    "registrationId": "reg_7f3ka9",
    "email": "trader@example.com",
    "accountNumber": "PA-4821-07"
  }
}
```

The events available today:

| Event                  | Fires when                                                                                         |
| ---------------------- | -------------------------------------------------------------------------------------------------- |
| `registration.linked`  | A trader signed in and linked an account your firm registered.                                     |
| `registration.revoked` | Your firm revoked a pending registration.                                                          |
| `account.reset`        | An evaluation account your firm issued was reset.                                                  |
| `balance.recorded`     | A credit, debit, or adjustment landed on one of your accounts.                                     |
| `risk.locked`          | Trading was locked on one of your accounts: a risk breach, an evaluation breach, or your own halt. |
| `risk.unlocked`        | That lock cleared.                                                                                 |

An endpoint registered with an empty event list receives all of them, including events added
later. Name the events explicitly if you would rather opt in deliberately.

## Verifying the signature

Every delivery carries a `trdrs-signature` header:

```
trdrs-signature: t=1787598252,v1=3f9a1c...64 hex characters
```

`v1` is an HMAC-SHA256, keyed with your endpoint's signing secret, over the string
`<t>.<raw request body>`. The timestamp is inside the signed material, so a captured body cannot be
replayed under a fresh clock.

Verify against the **raw bytes** you received. Parsing the JSON and re-serializing it will change
the whitespace and the signature will not match.

```ts theme={null}
import { createHmac, timingSafeEqual } from 'node:crypto'

function verify(secret: string, header: string, rawBody: string): boolean {
  const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header)
  if (!m) return false
  const t = Number(m[1])
  if (Math.abs(Math.floor(Date.now() / 1000) - t) > 300) return false // 5-minute freshness window
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest()
  const given = Buffer.from(m[2]!, 'hex')
  return expected.length === given.length && timingSafeEqual(expected, given)
}
```

The signing secret is returned when you create the endpoint and on every later read of it, because
it authenticates us to your endpoint and grants no access here. Store it with your other secrets
all the same.

## Retries, and what your handler owes us

Answer `2xx` and the delivery is done. Anything else, including a timeout, is a failure and we try
again: after 1 minute, 5 minutes, 30 minutes, 2 hours, and 6 hours. That is six attempts across
roughly nine hours, which carries a receiver through a deploy or a short outage. After the last
attempt the delivery is marked failed and stays in the delivery log for you to read.

Two consequences worth designing for:

**Answer fast, work later.** We wait five seconds for a response. Acknowledge the delivery, then do
your processing on your own time. A handler that does its work before responding will time out and
be retried even though it succeeded.

**Handle duplicates.** Because a timeout is indistinguishable from a failure, a retry can deliver an
event your handler already processed. Every payload carries a natural key for this: `referenceId` on
balance and reset events, `registrationId` on registration events, `accountNumber` on risk events. Key your
processing on those rather than assuming each delivery is new.

We refuse to dial private or reserved addresses, and we do not follow redirects. Point the endpoint
at its real public URL.

## When something is not arriving

`POST /api/partner/webhooks/test` sends a signed ping to one of your endpoints immediately and
reports exactly what your server answered, which separates "our delivery is broken" from "your
handler rejected it."

`GET /api/partner/webhooks/deliveries` is the log: every delivery for an endpoint, newest first,
with its status, attempt count, the HTTP status your server returned, and the last error. Terminal
deliveries are kept for 60 days. A delivery still pending retry is never aged out.

The live routes are in API Reference under Webhooks.
