Getting Started
Guides
REST API: Campaigns
Affiliates
Conversions
Affiliate Teams
SCALE
Coupons
Payouts
Other
Webhooks
Outbound Webhooks
ULTIMATE
Events
Payload Format
Signature Verification
GET
Manage Subscriptions
POST
Rotate Secret
POST
Test Webhook
MCP Server (AI)
Receive real-time HTTP notifications when events happen in your affiliate program.
Configure a webhook URL and select which events you want to receive. When an event occurs, we POST a JSON payload to your URL with an HMAC-SHA256 signature for verification. Create and manage webhooks from the Event Webhooks section of your dashboard's Developer API page, or via the REST API: POST /api/v1/webhooks to create, PATCH /api/v1/webhooks/:id to update url, events, or the active flag, and GET /api/v1/webhooks/:id/deliveries to inspect recent delivery attempts.
| Event | Description |
|---|---|
| conversion.created | A new conversion was tracked (Stripe, Paddle, Lemon Squeezy, Gumroad, GoPay, Apple In-App Purchases, the tracking script, the dashboard, or the API) |
| conversion.updated | A conversion status changed (catch-all for any status transition) |
| conversion.approved | A conversion was approved by the merchant |
| conversion.rejected | A conversion was rejected by the merchant |
| conversion.paid | A conversion was paid out to the affiliate |
| affiliate.joined | An affiliate joined one of your campaigns (join status is included in the payload) |
| affiliate.approved | A pending affiliate was approved |
| affiliate.rejected | A pending affiliate was rejected |
| affiliate.payout_details_completed | An affiliate completed their payout details (PayPal/Wise email saved, or Stripe payouts enabled). Fires once per affiliate |
| payout.completed | An affiliate payout was successfully processed |
| payout.failed | An affiliate payout attempt failed |
| bonus.awarded | A team bonus award was finalized after its refund-safe window and is awaiting merchant approval (Affiliate Teams, Scale) |
| bonus.paid | A team bonus award was paid out to the affiliate, via Stripe Connect or recorded as a manual payout (Affiliate Teams, Scale) |
| bonus.cancelled | A team bonus award was cancelled before payout, by a merchant action, a refund shrinking the award, or a plan downgrade (Affiliate Teams, Scale) |
{
"event": "conversion.created",
"data": {
"conversionId": "stripe_7PtIfIqd_123",
"campaignId": "PAbeJtXF39gDMUPYGZqZX",
"affiliateId": "lRCbRMSvBcGg4HszLCXLr",
"amount": "100.00",
"taxAmount": null,
"commission": "20.00",
"currency": "usd",
"status": "pending"
},
"timestamp": "2026-04-13T10:30:00Z",
"id": "wh_del_abc123"
}taxAmount is null unless the merchant calculates commission on the net sale (excluding tax). When set, amount is the net figure and the gross sale is amount + taxAmount.
bonus.awarded, bonus.paid, and bonus.cancelled (Affiliate Teams, Scale plan) carry a bonus-shaped payload instead of the conversion shape:
{
"event": "bonus.awarded",
"data": {
"awardId": "award_4Jd0Pc",
"goalId": "goal_2Nw6Vx",
"goalName": "July revenue push",
"teamId": "team_7bKq2mZ",
"teamName": "EU Growth Squad",
"affiliateId": "aff_5Yh1Qn",
"affiliateEmail": "jane@example.com",
"affiliateName": "Jane Smith",
"amount": "250.00",
"currency": "usd",
"periodKey": "2026-07",
"status": "pending_approval",
"payoutMethod": null
},
"timestamp": "2026-08-01T00:00:00Z",
"id": "wh_del_def456"
}Every webhook delivery includes an X-LinkJolt-Signature header in the form t=TIMESTAMP,v1=HEX. Verify it with an HMAC-SHA256 of `${timestamp}.${rawBody}` using your webhook secret, and reject anything older than 5 minutes for replay protection.
const crypto = require('crypto');
function verifyWebhook(rawBody, sigHeader, secret) {
const parts = Object.fromEntries(
sigHeader.split(',').map(p => p.split('='))
);
const { t: timestamp, v1: signature } = parts;
if (!timestamp || !signature) return false;
// Replay protection: reject if older than 5 minutes
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
if (Math.abs(age) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signature, 'hex')
);
}Each event is delivered once on a best-effort basis. If your endpoint returns a non-2xx response or times out, the failure is recorded but not automatically retried. Every attempt appears in the delivery log (dashboard, or GET /api/v1/webhooks/:id/deliveries), and failed deliveries can be resent from the dashboard log, so you can recover missed events without re-fetching from the REST API. After 5 consecutive failures the subscription is paused automatically and the account is notified by email and in-app. Re-enable it from the Event Webhooks section of your dashboard, or with PATCH /api/v1/webhooks/:id {"active": true}, which also resets the failure count.