Guides

Verify signatures

How your customers confirm a webhook really came from you, using the Standard Webhooks scheme.

Anyone can post to a customer’s URL. A signature lets the customer prove a request came from you and wasn’t altered. Dispatch signs every delivery using the Standard Webhooks scheme, so your customers can verify with any of its open-source libraries (the same ones Svix customers use) instead of writing cryptography.

Headers

Each delivery carries:

Header Meaning
webhook-id The message ID (msg_…). The same on every retry, so use it to ignore duplicates.
webhook-timestamp When this attempt was sent, in Unix seconds. Reject requests that are too old.
webhook-signature One or more space-separated v1,<base64> signatures.
x-ensue-delivery-id The delivery’s own ID, for support requests.

How the signature is made

  1. Take the string "<webhook-id>.<webhook-timestamp>.<raw body>".
  2. Compute its HMAC-SHA256 with the endpoint’s secret. The secret is whsec_ followed by base64; decode the base64 to get the key bytes.
  3. Base64-encode the result and prefix it with v1,.

The header lists more than one signature during a secret rotation. A request is genuine if any of them matches.

Verify with a library

Your customer installs a Standard Webhooks library and passes it the secret, the headers and the raw body.

import { Webhook } from "standardwebhooks";

const wh = new Webhook(process.env.ENSUE_WEBHOOK_SECRET); // "whsec_…"

app.post("/hooks", express.raw({ type: "application/json" }), (req, res) => {
  try {
    const event = wh.verify(req.body.toString(), {
      "webhook-id": req.header("webhook-id"),
      "webhook-timestamp": req.header("webhook-timestamp"),
      "webhook-signature": req.header("webhook-signature"),
    });
    // event.type, event.data …
    res.sendStatus(200);
  } catch {
    res.sendStatus(400);
  }
});

Libraries exist for JavaScript, Python, Go, Java, Ruby, PHP, Rust, C# and more. The library also rejects a timestamp outside its tolerance (five minutes by default), which stops an attacker replaying a captured request later.

What to tell your customers

  1. Verify before you parse. Use the exact raw body bytes. Re-serialising JSON changes the bytes and breaks the signature.
  2. Keep the secret secret. It’s shown with the endpoint, and can be rotated at any time.
  3. Return 2xx quickly, then process in the background. Anything else is retried. See Retries and recovery.
  4. De-duplicate on webhook-id. Delivery is at least once.

Finding the secret

GET /v1/dispatch/endpoints/:id/secret returns an endpoint’s current secret. Rotation keeps the previous secret signing for 24 hours, so customers can move to the new one with no downtime.