Skip to main content

Outbound Webhooks

Endpoint setup, event catalog, payload format, signature verification, and delivery behavior

Table of Contents

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:

  1. Go to Settings -> Webhooks.
  2. Create an endpoint with a name and destination URL.
  3. Select event types.
  4. Copy the signing secret shown after creation.
  5. Send a test delivery before relying on production events.

Each endpoint has its own signing secret and event subscriptions.

Event catalog

EventSubjectDescription
affiliate.createdAffiliateAn affiliate record was created
affiliate.approvedAffiliateAn affiliate moved to active state
referral.createdReferralA referral was created
referral.convertedReferralA referral reached conversion state
commission.createdCommissionA commission was created
commission.dueCommissionA commission moved to due state
commission.voidedCommissionA commission was voided
payout.paidPayoutA 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

CodeMeaning
private_uriThe endpoint resolved to a private, loopback, link-local, or multicast address
invalid_uriThe endpoint URL could not be parsed
timeoutThe connection or read timed out
dns_errorDNS resolution failed
ssl_errorTLS verification or negotiation failed
http_<status>The endpoint returned a non-2xx HTTP status

Receiver best practices

  • Return 2xx only 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.