Skip to main content

Receiving Webhooks

Every Refersion event is delivered to your endpoint as an HTTP POST with a JSON body. This page covers what that endpoint has to do to accept a delivery, and how to prove the request came from Refersion.

Your endpoint

Register a stable HTTPS URL that accepts a JSON POST body. The URL should not change between deployments, because it is stored against your webhook configuration and every event for that subscription is sent to it.

Responding to a delivery

Return a status code in the 2xx range (200-299) to acknowledge a delivery. Any other status is treated as a delivery failure and the message is retried, including 3xx redirects — a redirect is not followed, so pointing your endpoint at a URL that redirects means every delivery fails.

Your endpoint has 15 seconds to respond. If it does not answer within that window the delivery is counted as a failure and retried.

Acknowledge first, then process. Return your 2xx response as soon as you have stored or queued the message, and do the real work afterwards, so slow processing never consumes the response window and turns a successful delivery into a retry.

Expect the same message more than once. A retry fires whenever an acknowledgement fails to reach us, including when your endpoint already handled the delivery, and a portal replay resends messages deliberately. Every attempt at a message carries the same svix-id, so verify the signature first, then record that id under a unique constraint in the same transaction that stores or queues the work. If the id is already recorded, return 2xx without queueing it again, and keep the downstream processing idempotent so a redelivery cannot apply the same business event twice.

note

The exact retry timings and the response window described in these guides may change.

Cross-site request forgery protection

Deliveries are server-to-server requests. They carry no cookie and no session, so a CSRF token can never be present. Disable CSRF protection on the route that receives webhooks — most web frameworks enable it by default for POST routes, and leaving it on rejects every delivery before your code runs.

Verifying the signature

Each delivery carries three headers:

  • svix-id — a unique identifier for the message.
  • svix-timestamp — the Unix timestamp at which the message was sent.
  • svix-signature — one or more HMAC-SHA256 signatures over the message, computed with your endpoint's signing secret.

Prepare the key. The signing secret is issued as a whsec_-prefixed string. Strip the whsec_ prefix and Base64-decode what remains. Those decoded bytes are the HMAC key — not the printable string you copied out of the portal.

Build the signed content. Join the message id, the timestamp and the raw request body with periods:

{svix-id}.{svix-timestamp}.{raw request body}

Use the body exactly as it arrived. Re-serializing the parsed JSON changes the bytes and the signature will not match.

Compute the expected signature. Take the HMAC-SHA256 of that string with the decoded key, then Base64-encode the raw digest. Base64, not hex — a hex digest never matches.

Compare against the header. svix-signature holds a space-separated list of versioned entries, each written as {version},{signature}, for example:

v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= v1,bm9ldHUjKY8ZtTu7fAym7lWZDdU/POqk+2tHOJTMbEQ=

More than one entry appears while a secret is being rotated, and future versions may be added. Split the header on spaces, discard any entry whose version is not v1, and accept the delivery when your expected signature matches any remaining entry under a constant-time comparison. Reject it when none match.

Check the timestamp. Compare svix-timestamp against your own clock and reject anything outside a 5-minute tolerance. The timestamp is part of the signed content, so it cannot be altered without invalidating the signature; the tolerance is what makes a captured request unusable once it falls outside the window.

If your language has a maintained webhook-verification library that implements this scheme, prefer it over a hand-rolled implementation.

Retrieve and rotate an endpoint's signing secret in the webhook settings portal. Merchants open it from Settings > Webhooks; marketplace affiliates open it from "Your Name" > Edit Your Profile. Rotating a secret is the correct response to a secret you believe has leaked.

note

Integrations built before the current signing scheme may still be verifying an X-Refersion-Hmac-Sha256 header. Current deliveries do not send it. Verify the three headers above instead.