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

# Webhooks

> Receive a signed POST to your endpoint when a click, lead, sale, referral or commission happens.

Webhooks push events to your own endpoint in real time — no polling. Subscribe to the events you
care about and TrackRev sends each one as a signed JSON `POST`.

## Create a webhook

<Steps>
  <Step title="Open Integrations → Webhooks">
    Go to **Configuration → Integrations → "4 · Webhooks"**.
  </Step>

  <Step title="Add your endpoint">
    Enter an **`https://`** URL (HTTP is rejected) and select the events to subscribe to.
  </Step>

  <Step title="Save your signing secret">
    TrackRev shows the endpoint's signing secret (`whsec_…`) **once** — store it to verify
    signatures.
  </Step>
</Steps>

## Payload

Every delivery is a `POST` with this envelope:

```json theme={null}
{
  "event": "sale.created",
  "workspace_id": "ws_...",
  "created_at": "2026-07-21T09:20:00.000Z",
  "data": { "order_id": "o1...", "amount": 90.0, "currency": "usd", "email": "customer@example.com", "is_recurring": false, "provider": "stripe" }
}
```

Headers on every request:

| Header                 | Value                                    |
| ---------------------- | ---------------------------------------- |
| `Content-Type`         | `application/json`                       |
| `X-TrackRev-Event`     | The event name                           |
| `X-TrackRev-Signature` | HMAC-SHA256 of the raw body, hex-encoded |

## Verify the signature

Compute `HMAC-SHA256(rawBody, signingSecret)` and compare (constant-time) to
`X-TrackRev-Signature`:

```js theme={null}
import crypto from "node:crypto";

function verify(rawBody, signature, secret) {
  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
```

<Warning>
  Verify against the **raw** request body, exactly as received — not a re-serialised object — or the
  signature won't match.
</Warning>

## Events

| Event                      | Fires when                                            | `data`                                                                                        |
| -------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `click.created`            | Someone clicks a tracking link (humans only)          | `link_id, slug, channel, device, browser, os, country, referer`                               |
| `lead.created`             | A visitor is identified by email                      | `email, visitor_id`                                                                           |
| `sale.created`             | An attributed purchase comes in                       | `order_id, amount, currency, email, is_recurring, subscription_id, provider`                  |
| `referral.enrolled`        | A user opts into a referral programme                 | `program_id, partner_id, external_user_id, email, tier, referral_link`                        |
| `partner.first_commission` | A partner earns from their network for the first time | `partner_id, program_id, chain_level, earnings`                                               |
| `credit.earned`            | A referral qualifies an inviter for credit            | `id, program_id, partner_id, credit_kind, amount, external_user_id, referee_external_user_id` |
| `credit.delivered`         | A credit grant is delivered                           | same as `credit.earned`                                                                       |

## Delivery behaviour

* Each event is delivered to every active webhook subscribed to it.
* Delivery times out after 5 seconds and is best-effort — TrackRev records the last status but does
  not block your product on your endpoint.
* Return a `2xx` quickly; do heavy work asynchronously on your side.

<Note>
  Manage webhooks (add, disable, delete) from the same Integrations section. Each row shows its last
  delivery status and time.
</Note>
