Send webhooks to your app
Webhooks tell your server when a record changes. For example, a new customer can start a task in your app. Only program owners can manage webhooks.
Add an endpoint
- Open Webhooks in your dashboard.
- Choose Add endpoint.
- Enter a name and your server’s HTTPS address.
- Pick the events you need. Leave Enable endpoint checked.
- Choose Save endpoint.
- Copy the signing key. Store it on your server, then close the window.
Use a public HTTPS address on port 443. Local and private network addresses are blocked. A program can have 20 endpoints. Keep the signing key out of browser code and source control.

Event types
Each row has a created event and an updated event.
| Record | Created | Updated |
|---|---|---|
| Affiliate | partner.created | partner.updated |
| Customer | customer.created | customer.updated |
| Referral link | link.created | link.updated |
| Payment | transaction.created | transaction.updated |
| Commission | commission.created | commission.updated |
Updates are sent when a field in the webhook payload changes. Private settings do not produce an event. Deleting a record does not produce an event.
Read a payload
Each POST has data, type, and timestamp. The data includes the record ID and program ID. Other fields depend on the record. Fields with no saved value may be absent. Amounts use the currency’s smallest unit, such as cents for USD.
{
"data": {
"id": "customer-id",
"program_id": "program-id",
"name": "Taylor Smith",
"email": "taylor@example.test",
"status": "active"
},
"type": "customer.created",
"timestamp": "2026-09-11T12:00:00.000Z"
}| Record | Other fields |
|---|---|
| Affiliate | name, email, status, group_id, created_at, updated_at |
| Customer | name, email, identifier, status, partner_id, link_id, created_at, updated_at |
| Referral link | partner_id, parameter, value, destination, disabled, created_at, updated_at |
| Payment | customer_id, partner_id, amount, currency, refunded_amount, product_id, quantity, interval, billing_type, occurred_at, created_at |
| Commission | customer_id, partner_id, transaction_id, flow_id, amount, currency, reversed_amount, approval, settlement, created_at |
Verify the signature
Check the signature before you trust a payload. Use the raw request body. Parsing and rebuilding JSON can change the bytes and break the signature.
The headers are webhook-id, webhook-timestamp, and webhook-signature. The ID stays the same on retries. The header timestamp is in seconds and changes on each attempt.
This Node.js function checks the signature and rejects timestamps more than five minutes old or ahead. Pass your stored signing key, the request headers, and the raw body to it.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyWebhook(secret, headers, rawBody) {
const id = headers.get('webhook-id');
const timestamp = headers.get('webhook-timestamp');
const signature = headers.get('webhook-signature') ?? '';
if (!id || !timestamp || !/^\d+$/.test(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
if (!secret.startsWith('whsec_')) return false;
const expected = createHmac('sha256', Buffer.from(secret.slice(6), 'base64'))
.update(`${id}.${timestamp}.${rawBody}`)
.digest();
return signature.split(' ').some(value => {
const [version, encoded] = value.split(',');
if (version !== 'v1' || !encoded) return false;
const actual = Buffer.from(encoded, 'base64');
return actual.length === expected.length && timingSafeEqual(actual, expected);
});
}Save the event ID in your database with your work. Use a unique key or a transaction so two copies cannot do the same work twice. Return a 2xx response after the event is safely saved. Delivery can arrive more than once or out of order.
Check a delivery
Open Webhooks, then read Deliveries. Choose View payload and attempts to see the event and each response. Use Load older deliveries or Load older attempts to reach older history.
Your server must respond within 15 seconds. Any 2xx response means success. Redirects do not count as success. After a failure, the next waits are 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, and 10 hours. There are at most eight attempts in one cycle. The worker may run later during a backlog.
A receiver can return the header webhook-delivery: abort-message with a failed response to stop automatic retries for that message.
Stop repeated failures
By default, an endpoint is disabled after five days of ongoing failures. The clock starts only after failures span at least 12 hours within one day. A successful delivery clears the clock.
You can turn this off in Edit endpoint. If an endpoint is disabled, fix your receiver and choose Enable. Replay the messages you still need. Changing the endpoint or replacing its key also resets the clock.

Replay messages
For one message, choose Replay message. A message that is still queued or sending cannot be replayed yet.
For a date range:
- Choose Replay by date on an enabled endpoint.
- Enter the start and end time in UTC. Choose up to 31 days in the past.
- Leave the checkbox off to retry failed or canceled deliveries. Turn it on to send events with no delivery record for this endpoint.
- Choose Start replay.
- If another page is available, choose Replay next page until the form says it is complete.
Each page checks up to 100 events. Only the endpoint’s selected event types are queued. Missing-event replay can only use events saved after your program first added a webhook endpoint. It cannot recover earlier history. Replay keeps the original event ID and payload.
Change or remove an endpoint
Edit endpoint changes its address or events. Disable stops new deliveries. Delete endpoint removes it from the list but keeps delivery history. Changes stop queued messages from using old settings. A request already in progress may still finish.
Replace signing key creates a new key. Update your receiver, then replay any canceled messages you still need. You cannot view an old key after closing its window.