Outbound Webhooks
Outbound webhooks send signed AffiliateBase events to your application when affiliate, referral, commission, or payout activity changes.
Use webhooks when you need event-driven updates. Use the REST API when you need to query current state or backfill historical data.
Endpoint setup
Webhook endpoints are managed in the AffiliateBase app:
- Go to Settings -> Webhooks.
- Create an endpoint with a name and destination URL.
- Select event types.
- Copy the signing secret shown after creation.
- Send a test delivery before relying on production events.
Each endpoint has its own signing secret and event subscriptions.
Event catalog
| Event | Subject | Description |
|---|---|---|
affiliate.created | Affiliate | An affiliate record was created |
affiliate.approved | Affiliate | An affiliate moved to active state |
referral.created | Referral | A referral was created |
referral.converted | Referral | A referral reached conversion state |
commission.created | Commission | A commission was created |
commission.due | Commission | A commission moved to due state |
commission.voided | Commission | A commission was voided |
payout.paid | Payout | A payout was marked paid |
AffiliateBase creates one delivery per subscribed active endpoint for each event.
Request
AffiliateBase sends a POST request to your endpoint URL.
Headers:
Content-Type: application/json
User-Agent: AffiliateBase-Webhooks/1.0
svix-id: webhook_delivery_id
svix-timestamp: unix_timestamp
svix-signature: v1,base64_hmac_sha256_signature
Body:
{
"id": "webhook_event_id",
"type": "affiliate.created",
"occurred_at": "2026-05-29T14:21:12Z",
"data": {
"id": "affiliate_id",
"email": "[email protected]",
"state": "active",
"campaign_id": "campaign_id",
"created_at": "2026-05-29T14:21:12Z"
}
}
The id field identifies the outbound event. Store it and process it idempotently.
Signature verification
The svix-signature header is computed from:
svix-id.svix-timestamp.raw_request_body
Use HMAC SHA-256 with your endpoint signing secret, then Base64 encode the digest.
Node.js example:
import crypto from "node:crypto";
export function verifyAffiliateBaseWebhook(req, rawBody, signingSecret) {
const messageId = req.headers["svix-id"];
const timestamp = req.headers["svix-timestamp"];
const signatureHeader = req.headers["svix-signature"] || "";
const expected = crypto
.createHmac("sha256", signingSecret)
.update(`${messageId}.${timestamp}.${rawBody}`)
.digest("base64");
const received = signatureHeader.replace(/^v1,/, "");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}
Ruby example:
require "base64"
require "openssl"
def valid_affiliatebase_webhook?(headers:, raw_body:, signing_secret:)
message_id = headers.fetch("svix-id")
timestamp = headers.fetch("svix-timestamp")
signature = headers.fetch("svix-signature").delete_prefix("v1,")
signed_content = "#{message_id}.#{timestamp}.#{raw_body}"
expected = Base64.strict_encode64(
OpenSSL::HMAC.digest("SHA256", signing_secret, signed_content)
)
ActiveSupport::SecurityUtils.secure_compare(expected, signature)
end
Always verify against the raw request body. Do not verify against parsed JSON.
Delivery behavior
A delivery is successful when your endpoint returns a 2xx status code.
AffiliateBase records:
- request headers
- request body
- response status
- response headers
- response body excerpt
- error code, if the request failed before a response
- attempt timing
Failed deliveries can be retried from Settings -> Webhooks.
Error codes
| Code | Meaning |
|---|---|
private_uri | The endpoint resolved to a private, loopback, link-local, or multicast address |
invalid_uri | The endpoint URL could not be parsed |
timeout | The connection or read timed out |
dns_error | DNS resolution failed |
ssl_error | TLS verification or negotiation failed |
http_<status> | The endpoint returned a non-2xx HTTP status |
Receiver best practices
- Return
2xxonly after accepting the event. - Enqueue slow work and respond quickly.
- Store processed event IDs for idempotency.
- Reject invalid signatures.
- Reject stale timestamps to reduce replay risk.
- Log the event type and event ID, not the signing secret.