> ## 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.

# Create a webhook

> Registers an https endpoint; the platform POSTs events to it as they happen. Omit `events` to receive everything, including event types added later; name a subset to filter. Five endpoints per firm.

The event catalog today: `registration.linked`, `registration.revoked`, `account.reset`, `balance.recorded`, `risk.locked`, `risk.unlocked`.

The response carries the signing `secret` — it is shown on every read, not once, because it authenticates us to your endpoint and grants no access here. Verify every delivery with it:

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

// rawBody is the exact bytes we sent — verify before JSON.parse, not after.
function verify(secret: string, header: string, rawBody: string): boolean {
  const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header)
  if (!m) return false
  if (Math.abs(Math.floor(Date.now() / 1000) - Number(m[1])) > 300) return false // stale
  const expected = createHmac('sha256', secret).update(`${m[1]}.${rawBody}`).digest()
  const given = Buffer.from(m[2], 'hex')
  return expected.length === given.length && timingSafeEqual(expected, given)
}
```

Answer `2xx` to accept a delivery. Anything else is retried with backoff (about nine hours in total), so your endpoint may see the same event twice — key your handler on the delivery `id`.



## OpenAPI

````yaml /partner-platform/openapi.json post /api/partner/webhooks
openapi: 3.1.0
info:
  title: trdrs Engine API
  version: 1.1.0
  description: >-
    ## API Reference


    This is the served OpenAPI contract for the trdrs engine: market data,
    trading and account

    routes for your firm's traders, trdrs Connect account-registration handoff,
    and preview

    challenge routes.


    See Quick Start for the first integration path: key setup, market config,
    chart data, Connect

    account registrations, idempotency, stream reconnects, and conformance.


    See Overview and API Standards for the cross-cutting contract rules.
servers:
  - url: https://app.trdrs.co
    description: Production
  - url: /
    description: This engine
security: []
tags:
  - name: Market data
    description: >-
      Symbol search and resolution, OHLCV history, quote snapshots, the server
      clock, and the live bar stream. Crypto rides each venue’s public feed;
      futures stream from the caller’s own connected Rithmic account. With none
      connected, futures requests answer 503 `feed_requires_connection`.
  - name: News
    description: >-
      Aggregated market news and the economic calendar, from licensed/open
      sources, keyword-tagged with futures roots at ingest. Platform-wide
      content (nothing per-user), admitted exactly like Market data: a licensed
      origin, a session, or a firm API key. Headlines page by published time,
      scope by instrument root, and stream live over SSE; thumbnails serve
      through the image proxy.
  - name: Trading
    description: >-
      The money surface: entries, exits, replaces, cancels, and position/account
      flattening. Every order-placing call uses `clientOrderId` as its
      idempotency key.
  - name: Account
    description: >-
      Reading a connected account. You do not create trading accounts here: a
      trader connects their own broker account (or creates a free demo account)
      in the app, and firms create evaluation accounts through the Partner API
      (Firm accounts → Create evaluation accounts) or register venue accounts
      through Connect (Create an account registration). Account state and the
      durable ledgers: balances, positions, working orders, fills, P&L history,
      and the live account stream.
  - name: Connect
    description: >-
      trdrs Connect account registration, for partner firms. Register an account
      you issued on your own venue — a pending account registration — with the
      trader’s sign-in email, optionally the venue account id and the login name
      your venue issued. The trader finds it waiting in the connect flow the
      moment they sign in with that email: the connect step is pre-filled with
      everything except the credential, which the trader always enters
      themselves. A registration never transmits a password and never grants
      access to anything before the trader’s own login succeeds. These routes
      answer a partner-scoped key only; a firm API key or a user session gets
      401. Registrations expire after 30 days; re-registering the same email +
      account refreshes the expiry instead of duplicating. The end-to-end flow
      guide is **[Quick Start](/docs/guides/quick-start)**.
  - name: Firm accounts
    description: >-
      Evaluation accounts your firm issues on the trdrs venue, through your
      partner key — the other half of account setup. Connect registrations hand
      off accounts that exist on your venue; these routes create and manage
      accounts on ours: the trader trades them on trdrs, and your firm owns the
      lifecycle. Every route is scoped to accounts your firm created through
      this API — an account the same trader opened themselves is invisible and
      untouchable here, by construction. Creation is batched with per-item
      results, and every write carries your own `referenceId`, so a crashed
      pipeline retries safely. Served when the deployment runs the prop engine;
      without it, every route in this group answers `404`.
  - name: Webhooks
    description: >-
      The outbound event bus: register an https endpoint and the platform pushes
      events to it instead of your back office polling us. Every delivery is
      signed (`trdrs-signature: t=<unix>,v1=<hmac-sha256>` over
      `${t}.${rawBody}`) so you can prove it came from us and is fresh, and
      every delivery is durable — a failed attempt is retried with backoff for
      about nine hours and the whole log is readable, so an endpoint that was
      down is a delay rather than a lost event. Serves brokers and prop firms
      alike: the account-registration (`registration.*`) events fire wherever
      Connect does, and the account events fire where the prop engine runs.
  - name: Challenges
    description: >-
      The prop evaluation surface: challenge programs and a trader’s own
      enrollments. **Preview: the one group on this page outside the
      additive-only guarantee** (the pre-contract v1 scaffold; the Phase-1
      rebuild will change these shapes; see Stability). **Cookie-authenticated,
      not key-authenticated**, and served only when the engine runs with
      `CHALLENGES_ENABLED`; without that flag the bundle is absent and every
      route below returns `404`. The firm-console/admin half of this surface is
      deliberately not documented here. It is back office, not licensed surface.
paths:
  /api/partner/webhooks:
    post:
      tags:
        - Webhooks
      summary: Create a webhook
      description: >-
        Registers an https endpoint; the platform POSTs events to it as they
        happen. Omit `events` to receive everything, including event types added
        later; name a subset to filter. Five endpoints per firm.


        The event catalog today: `registration.linked`, `registration.revoked`,
        `account.reset`, `balance.recorded`, `risk.locked`, `risk.unlocked`.


        The response carries the signing `secret` — it is shown on every read,
        not once, because it authenticates us to your endpoint and grants no
        access here. Verify every delivery with it:


        ```ts

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


        // rawBody is the exact bytes we sent — verify before JSON.parse, not
        after.

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

        ```


        Answer `2xx` to accept a delivery. Anything else is retried with backoff
        (about nine hours in total), so your endpoint may see the same event
        twice — key your handler on the delivery `id`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PartnerWebhookCreateRequest'
      responses:
        '200':
          description: >-
            The registered endpoint, including its signing secret
            (PartnerWebhookResponse)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PartnerWebhookResponse'
        '400':
          description: >-
            A non-https or malformed URL, or an unknown event type. The error
            names the valid types.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: The bearer is not a partner-scoped key for an active partner firm
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Webhooks are not enabled on this deployment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Five endpoints already registered — delete one first
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - partnerKey: []
      x-codeSamples:
        - lang: javascript
          label: TypeScript
          source: >-
            const res = await fetch('https://app.trdrs.co/api/partner/webhooks',
            {
              method: 'POST',
              headers: {
                'content-type': 'application/json',
                Authorization: `Bearer ${process.env.TRDRS_API_KEY}`,
              },
              body: JSON.stringify({
                "url": "https://ops.yourfirm.example/hooks/trdrs",
                "events": [
                  "registration.linked",
                  "risk.locked",
                  "balance.recorded"
                ]
              }),
            })

            const data = await res.json()
        - lang: shell
          label: cURL
          source: |-
            curl -X POST 'https://app.trdrs.co/api/partner/webhooks' \
              -H "Authorization: Bearer $TRDRS_API_KEY" \
              -H 'content-type: application/json' \
              -d '{"url":"https://ops.yourfirm.example/hooks/trdrs","events":["registration.linked","risk.locked","balance.recorded"]}'
components:
  schemas:
    PartnerWebhookCreateRequest:
      type: object
      description: >-
        PartnerWebhookCreateRequest. Register one endpoint. Omit `events` to
        receive everything; five endpoints per firm.
      properties:
        url:
          type: string
          description: >-
            An https:// URL (≤500 chars). Plain http is refused, and a host
            resolving to a private address is refused at delivery time.
        events:
          type: array
          items:
            type: string
            enum:
              - registration.linked
              - registration.revoked
              - account.reset
              - balance.recorded
              - risk.locked
              - risk.unlocked
          description: >-
            Optional filter. Omitted or empty = every event type, including ones
            added later.
      required:
        - url
      example:
        url: https://ops.yourfirm.example/hooks/trdrs
        events:
          - registration.linked
          - risk.locked
          - balance.recorded
    PartnerWebhookResponse:
      type: object
      properties:
        webhook:
          $ref: '#/components/schemas/PartnerWebhook'
      required:
        - webhook
      example:
        webhook:
          id: whk_4d19c2
          url: https://ops.yourfirm.example/hooks/trdrs
          events:
            - registration.linked
            - risk.locked
            - balance.recorded
          enabled: true
          secret: trdrs_whsec_9Kb2xR7pQm4TvJhN6sYcAeWd
          createdAt: '2026-08-24T18:02:00Z'
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
      required:
        - error
      example:
        error: invalid_instrument
    PartnerWebhook:
      type: object
      description: >-
        PartnerWebhook. One registered endpoint. `secret` is returned on every
        read on purpose: it authenticates us to your endpoint and grants nothing
        here, and the only reader is your own partner key.
      properties:
        id:
          type: string
        url:
          type: string
          description: Your https endpoint. We POST JSON here and never follow redirects.
        events:
          type: array
          items:
            type: string
          description: >-
            The event types this endpoint receives. An empty array means every
            event, including ones added later.
        enabled:
          type: boolean
        secret:
          type: string
          description: >-
            The HMAC signing secret (`trdrs_whsec_…`). Verify every delivery
            against it; treat it like a password.
        createdAt:
          type: string
          format: date-time
      required:
        - id
        - url
        - events
        - enabled
        - secret
        - createdAt
      example:
        id: whk_4d19c2
        url: https://ops.yourfirm.example/hooks/trdrs
        events:
          - registration.linked
          - risk.locked
          - balance.recorded
        enabled: true
        secret: trdrs_whsec_9Kb2xR7pQm4TvJhN6sYcAeWd
        createdAt: '2026-08-24T18:02:00Z'
  securitySchemes:
    partnerKey:
      type: http
      scheme: bearer
      description: >-
        A partner-scoped API key (`trdrs_sk_…`), issued to a trdrs Connect
        partner firm and accepted only under `/api/partner/`. Same format as the
        firm (`tenant`) key, different scope: a firm API key is refused here,
        and this key is refused everywhere else.

````