Outbound webhooks
Cobalz emits Standard Webhooks v1 — the spec from the Standard Webhooks project that Slack, Twilio, GitHub, and Svix helped define. You can verify signatures with any svix-compatible library in Node, Python, Ruby, Go, Rust, PHP, Java, .NET, or Elixir.
Add an endpoint
Settings → Webhooks → New endpoint. Paste your URL, pick the events you care about. The signing secret is shown exactly once on creation; rotate at any time.
Signature verification
Every request includes three headers:
webhook-id— unique message id for idempotency.webhook-timestamp— unix seconds. Reject if >5 min off your wall clock.webhook-signature—v1,<base64-hmac>, possibly multiple comma-separated values during key rotation.
# Pseudocode — verify_webhook_signature(secret, headers, body)
signed = f"{headers['webhook-id']}.{headers['webhook-timestamp']}.{body}"
expected = base64(hmac_sha256(secret, signed))
for sig in headers['webhook-signature'].split(','):
version, value = sig.split(',', 1)
if version == 'v1' and timing_safe_eq(value, expected):
return okRetries
On non-2xx responses we retry with this schedule:
- 5 seconds → 5 minutes → 30 minutes → 2 hours → 5 hours → 10 hours → 14 hours → 20 hours → 24 hours
- After 9 attempts, the delivery moves to
dead_letterand can be replayed manually from the endpoint detail page. - HTTP
410 Goneauto-disables your endpoint (no further deliveries). - Other 4xx (except 429) are treated as permanent client errors — no retry.
- HTTP
429respects yourRetry-Afterheader.
Event catalog
| type | data payload |
|---|---|
affiliate.approved | { affiliate_id, slug, email } |
affiliate.suspended | { affiliate_id, reason } |
commission.created | { commission_id, affiliate_id, amount, currency, order_id } |
commission.approved | { commission_id, affiliate_id, amount, currency } |
commission.refunded | { commission_id, affiliate_id, amount, currency, refund_amount } |
payout.sent | { payout_id, affiliate_id, amount, currency, rail, external_id } |
payout.paid | { payout_id, affiliate_id, amount, currency, rail } |
tax_form.submitted | { affiliate_id, form_type } |
fraud.flagged | { affiliate_id, reason } |
Replay + dead letters
Every delivery — including failed and dead-lettered ones — is visible at Settings → Webhooks → <endpoint>. Each row has a Replay button that re-emits the same payload (with a fresh webhook-id). Bulk-replay via SQL is documented in the webhook-deliveries-backlog runbook.