> For the complete documentation index, see [llms.txt](https://refkit.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://refkit.gitbook.io/docs/integrate-refkit/outgoing-webhooks.md).

# Outgoing webhooks

RefKit can send selected App lifecycle events to one HTTPS endpoint. Use these events to synchronize another system, run custom Affiliate automation, or start payout execution in an external finance system.

Delivery is best-effort and single-attempt. A failed or timed-out request never rolls back the RefKit operation that produced the event.

## Configure an endpoint

1. Open **App settings > Outgoing webhook**.
2. Enter the HTTPS endpoint URL.
3. Select the events your endpoint needs.
4. Select **Save webhook**.
5. Copy the signing secret. RefKit reveals it only when the endpoint is created or the secret is rotated.
6. Select **Send test** and confirm the request reaches your endpoint.

An App has one outgoing endpoint. You can disable delivery without removing the endpoint, rotate its secret, or inspect recent delivery results from the same card.

RefKit blocks private and local network destinations by default. A self-hosted deployment can allow them by setting `WEBHOOK_ALLOW_PRIVATE_NETWORKS=true`.

## Events

| Event                  | Sent when                                                 | `data.id` identifies |
| ---------------------- | --------------------------------------------------------- | -------------------- |
| `affiliate.created`    | An Affiliate joins or is added to a Program               | Program affiliate    |
| `affiliate.approved`   | A pending Affiliate is approved                           | Program affiliate    |
| `affiliate.disabled`   | An Affiliate is disabled                                  | Program affiliate    |
| `referral.created`     | An attributed Customer is identified                      | Referral             |
| `transaction.created`  | A payment transaction is recorded                         | Transaction          |
| `transaction.refunded` | A refund transaction is recorded                          | Refund transaction   |
| `commission.created`   | An earned commission entry is created                     | Commission entry     |
| `commission.reversed`  | A reversal commission entry is created                    | Commission entry     |
| `commission.paid`      | A commission entry is marked paid                         | Commission entry     |
| `payout.ready`         | A prepared payout batch is dispatched, once per Affiliate | Payout execution     |
| `payout.succeeded`     | A payout execution succeeds or is completed manually      | Payout execution     |
| `payout.failed`        | An external system reports a payout execution failure     | Payout execution     |

Selecting **Send test** sends `webhook.test`. It is not a subscribable lifecycle event.

`livemode` is `false` for Test activity and `true` for Live activity. Payout events are always Live.

## Payload

Every request uses the same top-level envelope:

```json
{
  "id": "whev_...",
  "type": "referral.created",
  "created_at": "2026-08-04T10:30:00.000Z",
  "livemode": true,
  "app_id": "app_...",
  "data": {
    "id": "ref_...",
    "customer_id": "rcus_...",
    "program_id": "prg_...",
    "program_affiliate_id": "aff_...",
    "click_id": "clk_...",
    "created_at": "2026-08-04T10:29:59.000Z"
  }
}
```

The `data` object is a snapshot of the affected resource. Use the top-level event `id` to make processing idempotent. Amounts use integer minor units and lowercase ISO currency codes.

## Headers

Each request is an HTTP `POST` with `Content-Type: application/json` and these headers:

| Header                       | Value                                        |
| ---------------------------- | -------------------------------------------- |
| `X-RefKit-Webhook-Id`        | Event ID, matching the payload `id`          |
| `X-RefKit-Webhook-Event`     | Event type, matching the payload `type`      |
| `X-RefKit-Webhook-Timestamp` | Unix timestamp in seconds                    |
| `X-RefKit-Webhook-Signature` | `v1=` followed by the HMAC-SHA256 hex digest |
| `X-RefKit-Webhook-Version`   | `1`                                          |

## Verify the signature

Read the exact raw request body before parsing JSON. Compute HMAC-SHA256 over:

```
timestamp.rawBody
```

For example, in Node.js:

```js
import { createHmac, timingSafeEqual } from "node:crypto";

function verifyRefKitWebhook({ rawBody, timestamp, signature, secret }) {
  if (!signature.startsWith("v1=")) return false;

  const expected = createHmac("sha256", secret)
    .update(timestamp)
    .update(".")
    .update(rawBody)
    .digest();
  const received = Buffer.from(signature.slice(3), "hex");

  return expected.length === received.length
    && timingSafeEqual(expected, received);
}
```

Reject requests with an invalid signature. Also reject timestamps outside a short tolerance, such as five minutes, to reduce replay risk.

## Delivery behavior

Return any `2xx` response within three seconds to mark the delivery successful. RefKit does not follow redirects or retry failed deliveries. It records the HTTP status and a truncated response or error in **App settings > Outgoing webhook**.

The originating Affiliate, Referral, transaction, commission, or payout operation commits before delivery. Webhook failure does not change that operation.

## External payout execution

Subscribe to `payout.ready` before dispatching a prepared payout batch. RefKit sends one event per Affiliate. The event identifies the payout execution but does not include payout instructions.

1. Read the execution ID from `data.id`.
2. Call `GET /v1/payout-executions/:id` with a Live API key scoped to the exact App.
3. Execute payment in your finance system.
4. Report the result to `/v1/payout-executions/:id/succeeded` or `/v1/payout-executions/:id/failed` with the same key and a unique `Idempotency-Key` header.

If `payout.ready` delivery fails, the execution stays ready. RefKit does not retry it automatically. Manual CSV export and **Mark paid** remain available. See [Commissions and payouts](/docs/run-your-program/commissions-payouts.md) for the complete payout flow.

The webhook configuration and delivery-history endpoints are listed in the [REST API reference](/docs/reference/rest-api.md#outgoing-webhooks).
