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 record’s public fields change. A payment-only change does not send a commission update. 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",
"organization_id": "organization-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, first_name, last_name, email, status, rejected_at, suspended_at, group_id, country_code, custom_fields, payout_method, created_at, updated_at |
| Customer | name, email, identifier, customer_id, status, partner_id, link_id, click_id, subscription_id, lead_at, active_at, created_at, updated_at |
| Referral link | partner_id, parameter, param, value, destination, disabled, created_at, updated_at |
| Payment | customer_id, partner_id, amount, currency, refunded_amount, charge_id, click_id, provider, product_id, product_name, quantity, interval, billing_type, occurred_at, created_at |
| Commission | customer_id, partner_id, transaction_id, flow_id, amount, currency, revenue, source, charge_id, reversed_amount, status, approval, settlement, payout_id, created_at |
For commissions, status and approval have the same value: pending, approved, or rejected. The settlement field tracks payment separately.
For customers, customer_id uses the app customer ID saved through the API. If no separate ID is saved, it uses identifier. The top-level id is the Referral Bear record ID.
Affiliate first_name and last_name use the saved separate name fields. Full-name records return null for both parts.
Affiliate custom_fields contains signup answers. Each answer has question_id, question, answer_id, and answer. The question text comes from the saved signup form. Multiple choices are joined with commas. The answer ID stays the same for that affiliate and question. country_code is the saved two-letter country code, when known.
New commission events include revenue from the linked payment, before refunds. Like amount, it uses the currency’s smallest unit. A $10 payment has revenue: 1000 in USD. source is stripe, paddle, or manual when known. A saved Stripe charge adds charge_id. These fields are left out when the payment is missing or does not match the commission’s customer, affiliate, and currency. Refunds are tracked in reversed_amount; they do not reduce revenue.
New events include the record ID, program ID and organization ID. These IDs tell your server which company owns the record. Retried deliveries keep the original payload.
For links, param and parameter have the same value. Payment references and product names are included when saved on the record. Bank details, signing keys, and login details are never included.
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.
Affiliate events include rejected_at only while rejected and suspended_at only while suspended. Leaving either status clears its date. Repeated status requests keep the saved date; older records without one return null.
Affiliate payment methods
Affiliate events include payout_method: none, paypal, wise, bank_transfer, wire, or crypto. It is the method saved for the affiliate’s current account when the event is queued. Missing or unrelated payment details return none.
Saving or clearing payment details creates an affiliate update, including when the method stays the same. Retries keep the original method. Bank details, payment addresses and payout_details are not included.