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
- Take the string
"<webhook-id>.<webhook-timestamp>.<raw body>". - Compute its HMAC-SHA256 with the endpoint’s secret. The secret is
whsec_followed by base64; decode the base64 to get the key bytes. - 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
- Verify before you parse. Use the exact raw body bytes. Re-serialising JSON changes the bytes and breaks the signature.
- Keep the secret secret. It’s shown with the endpoint, and can be rotated at any time.
- Return
2xxquickly, then process in the background. Anything else is retried. See Retries and recovery. - 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.