What are webhooks?
Webhooks let your application receive real-time HTTP POST notifications when events happen in your Anderro affiliate program - such as when an affiliate signs up through a referral link or when a commission is generated.
Setup
- Go to your product's settings in the Anderro dashboard
- Navigate to the Webhooks tab
- Enter your endpoint URL (must be HTTPS)
- Select the events you want to receive
- Save - Anderro will generate a webhook secret for signature verification
Webhook payload
All webhook deliveries are HTTP POST requests with a JSON body:
{
"id": "evt_cm1abc2def3",
"event": "affiliate.signup",
"created_at": "2026-04-08T12:00:00.000Z",
"data": {
"customer_email": "[email protected]",
"affiliate": {
"email": "[email protected]",
"name": "Jane Smith",
"referral_code": "jane-smith"
},
"product": {
"id": "cuid_abc123",
"name": "My SaaS"
}
}
}
Security headers
Every webhook request includes two headers for signature verification:
| Header | Description |
|---|---|
x-webhook-signature | HMAC-SHA256 signature of the payload |
x-webhook-timestamp | Unix timestamp when the webhook was sent |
Verifying signatures
To verify a webhook is genuinely from Anderro, compute HMAC-SHA256 over the timestamp, a dot, and the raw request bytes, then compare it with the header value. Do not re-serialize a parsed object: whitespace, escaping, or key order changes will invalidate the signature. In Express, register this route before any express.json() middleware:
import express from "express";
import { createHmac, timingSafeEqual } from "crypto";
const app = express();
const WEBHOOK_SECRET = process.env.ANDERRO_WEBHOOK_SECRET;
function verifyWebhook(rawBody, signature, timestamp, secret) {
if (typeof signature !== "string" || !/^[a-f0-9]{64}$/i.test(signature)) {
return false;
}
if (typeof timestamp !== "string" || !/^\d+$/.test(timestamp)) {
return false;
}
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return false;
}
const expected = createHmac("sha256", secret)
.update(`${timestamp}.`)
.update(rawBody)
.digest();
return timingSafeEqual(expected, Buffer.from(signature, "hex"));
}
app.post("/webhooks/anderro", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-webhook-signature"];
const timestamp = req.headers["x-webhook-timestamp"];
if (!verifyWebhook(req.body, signature, timestamp, WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
// Parse only after verifying the original bytes.
const event = JSON.parse(req.body.toString("utf8"));
console.log(`Received ${event.event}:`, event.data);
res.status(200).send("OK");
});
// Register JSON parsing for other routes after the webhook route.
app.use(express.json());
Timestamp validation
For additional security, verify that the timestamp is within an acceptable window (e.g., 5 minutes) to prevent replay attacks.
Delivery behavior
- Timeout: Anderro waits up to 5 seconds for your endpoint to respond
- Expected response: Return any
2xxstatus code to acknowledge receipt - Retries: When automatic retries are enabled, timeouts, connection errors,
5xx,408, and429responses are retried after 1 minute, 5 minutes, 30 minutes, 2 hours, and 12 hours, for 6 attempts total. These delays are measured from the preceding attempt; delivery may take slightly longer. Other4xxresponses and invalid or blocked URLs are not retried - Deduplication: The
x-webhook-idheader equals the payloadidand stays constant across attempts. Retries send the same payload with a fresh timestamp and signature; use the ID to avoid processing an event twice - Delivery log: The dashboard's Webhooks tab shows each delivery and its attempts, including the attempt count and next scheduled attempt
Test webhooks
You can send test webhooks from the dashboard to verify your endpoint is working correctly. Test events include a top-level "sandbox": true field in the payload so you can distinguish them from real events.
Available events
Tracking events
| Event | Description |
|---|---|
affiliate.signup | A customer clicked an affiliate's referral link and signed up |
affiliate.payment | A referred customer made a payment; the commission may be null |
Affiliate lifecycle events
| Event | Description |
|---|---|
affiliate.approved | A pending affiliate was approved to join your program |
affiliate.rejected | An affiliate application was rejected |
affiliate.removed | An affiliate was removed from your program |
Commission & payout events
| Event | Description |
|---|---|
commission.created | A new commission was generated from a referred payment |
commission.refunded | A commission was voided or reduced because the underlying payment was refunded |
commission.restored | A dispute clawback was reversed after the merchant won the dispute |
payout.created | A new payout was generated for an affiliate |
payout.completed | A payout was marked as paid |
Opt-in events
New event types are not automatically enabled. To receive new events, go to your product's Webhooks tab in the dashboard and select the events you want to subscribe to.