Reference
Webhooks
Push delivery for the event feed: commission and program state changes land on your HTTPS endpoint as signed POSTs. Subscribe via the REST API, the subscribe_webhook MCP tool, or the dashboard.
Event types
| Type | Fires when |
|---|---|
commission.created | a new conversion appears on any connected network |
commission.approved | a pending commission is validated by the network |
commission.reversed | a commission is declined or clawed back |
commission.paid | a commission is included in a payout |
program.approved | a program application is accepted |
program.terminated | a program relationship ends |
sync.auth_error | a network rejects your stored credentials (time to re-enter them) |
Events are diff-based: the sync engine compares each run against stored state, so a re-run never produces duplicate events.
Delivery format
Each delivery is a JSON POST:
{
"id": "5f0c7e0a-...",
"type": "commission.approved",
"created_at": "2026-07-09T02:14:00Z",
"payload": { "transaction_id": "...", "network": "awin",
"commission_amount": 12.4, "currency": "GBP",
"attribution_tag": "yt-desc-2026-07" }
}| Header | Contents |
|---|---|
X-AffiliateOS-Signature | HMAC of the raw body: sha256=<hex> |
X-AffiliateOS-Event | the event type |
X-AffiliateOS-Delivery | unique delivery id (stable across retries of the same delivery) |
Verifying signatures
The secret (whsec_...) is returned once when you create the endpoint. Compute an HMAC-SHA256 of the raw request body and compare in constant time:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody: string, header: string, secret: string): boolean {
const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(header);
return a.length === b.length && timingSafeEqual(a, b);
}Verify against the raw body bytes, not a re-serialized object; JSON key order matters to the digest.
Retries and failure handling
| Behavior | Value |
|---|---|
| Timeout per attempt | 15 seconds |
| Retry ladder after a failure | 1m, 5m, 30m, 2h, 12h, then the delivery is marked failed |
| Endpoint auto-disable | after 20 consecutive failed deliveries the endpoint is disabled until you re-enable it |
| Success criteria | any 2xx response; everything else counts as a failure |
Respond 2xx quickly and process asynchronously. If your endpoint is flaky, deliveries queue and retry; if it stays down, check the dashboard’s developers page for delivery history.
Prefer polling?
The same events are available pull-style at GET /v1/events or the check_events MCP tool, using the returned high-water mark as the after parameter. Agents working in-session should poll; webhooks are for standing automation.