Skip to content

2026-04-08 · Operator notes

Why we ship Standard Webhooks v1 in both directions

Custom webhook signing schemes were a 2017 thing. Here's why every modern API should adopt the Standard Webhooks v1 spec, and what we learned wiring it through 12 outbound event types.

The custom-signing tax

Every API that fires webhooks signs them somehow. Stripe uses Stripe-Signature: t=…,v1=… . GitHub uses X-Hub-Signature-256: sha256=…. Twilio uses X-Twilio-Signature with their own canonicalisation. PayPal, Slack, Shopify — every one of them signs differently.

For a developer integrating two or three of these, fine. For a developer integrating fifteen — say, anyone building a CRM or a workflow tool — the signing-scheme tax adds up. You write the verification code once per provider, maintain it as their docs drift, and pray you handle the edge cases (timestamp tolerance windows, key rotation, multiple active signatures during a key swap) correctly each time.

Standard Webhooks fixes this

Standard Webhooks v1.0.2 is a small, opinionated spec from the people behind Svix. It defines:

  • Three lowercase headers: webhook-id, webhook-timestamp,webhook-signature.
  • A canonical signed payload: ${id}.${timestamp}.${body}.
  • HMAC-SHA256, base64-encoded, prefixed with v1, in the signature header. Multiple signatures space-separated to support seamless key rotation.
  • A timestamp tolerance (typically 5 minutes) to defeat replay attacks.
  • Idempotency via the unique webhook-id — receivers dedupe on it.

Every receiver gets the same code path:

import { Webhook } from 'svix';

app.post('/webhooks/cobalz', (req, res) => {
  const wh = new Webhook(process.env.COBALZ_SECRET);
  try {
    const evt = wh.verify(req.rawBody, req.headers);
    // evt.type === 'commission.approved' etc.
  } catch {
    return res.status(401).send();
  }
  // ... your business logic
  res.status(200).send();
});

That's it. Three lines for verification. Drop in svix-libs (Python, Ruby, Go, Rust, Kotlin, .NET, PHP, Elixir all supported) and you're done.

What we shipped

Cobalz signs every outbound webhook with this spec — 12 event types as of today: affiliate.approved, affiliate.suspended,affiliate.tier_promoted, commission.created,commission.approved, commission.voided,payout.sent, payout.paid, fraud.flagged,tax_form.submitted, plus two more in the pipeline.

The implementation lives in the open atpackages/shared/src/standard-webhooks.ts. Sign-and-verify in 90 lines. We use it for outbound delivery to merchants' webhook endpoints, and our Resend webhook receiver verifies inbound Resend events the same way (Resend itself uses Svix under the hood, so it just works).

The retry policy is part of the spec

The recommended retry schedule from the Standard Webhooks docs is published and stable: 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, 14 hours, 20 hours, 24 hours. We follow it exactly. If your endpoint goes down for a day, we have ten attempts to deliver, spread to give you headroom to recover. After max attempts, the event lands in our dead-letter store with a one-click replay button in the dashboard.

When a receiver returns 410 Gone, we automatically disable the endpoint and dead-letter every pending delivery. When it returns 5xxor 429 we honour Retry-After. When it returns other 4xx (which means the receiver permanently rejected the payload — usually a bug on their side), we dead-letter immediately rather than retry their bug.

Why this matters for affiliates

An affiliate program is a ledger. Every event that changes that ledger (commission accrued, commission approved, refund clawed back, payout sent, payout paid) needs to flow into the merchant's downstream systems — accounting tools, business-intelligence dashboards, Slack channels, CRM workflows — exactly once and at the right time.

Custom signing schemes turn that into a special-snowflake integration project. Standard Webhooks turns it into “point svix-libs at the URL we give you and you're done.”

Spec details: /features/webhooks

Webhooks that don't drop events.

Standard Webhooks v1 outbound on every Growth-plan-and-up account. svix-libs verification works out of the box.