Verifying Webhook Signatures
StudioBase signs every webhook delivery following the Standard Webhooks specification, so you can verify with an off-the-shelf Standard Webhooks library or a few lines of code. Always verify signatures before trusting a payload.
Your endpoint's signing secret (format whsec_<base64>) is shown in
Settings → Integrations → Webhooks when you create the endpoint.
Delivery headers
| Header | Description |
|---|---|
webhook-id | The event UUID — stable across retries; deduplication key |
webhook-timestamp | Unix timestamp (seconds) of this delivery attempt |
webhook-signature | v1,<base64 HMAC-SHA256> of the signed content |
The signed content is the concatenation:
{webhook-id}.{webhook-timestamp}.{raw request body}
The HMAC key is the base64-decoded portion of your secret after the
whsec_ prefix.
Verification example (Node.js)
const crypto = require("crypto");
function verifyWebhook({ secret, headers, rawBody }) {
const id = headers["webhook-id"];
const timestamp = headers["webhook-timestamp"];
const signatureHeader = headers["webhook-signature"] ?? "";
// Reject stale deliveries to prevent replay (5-minute tolerance).
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const key = Buffer.from(secret.slice("whsec_".length), "base64");
const expected = crypto
.createHmac("sha256", key)
.update(`${id}.${timestamp}.${rawBody}`, "utf8")
.digest("base64");
// The header may contain multiple space-separated signatures.
return signatureHeader.split(" ").some((entry) => {
const [version, signature] = entry.split(",");
return (
version === "v1" &&
signature?.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
);
});
}
Verify against the raw request body bytes — re-serializing the parsed JSON can change key order or whitespace and break the signature.
Retries and idempotency
- A delivery succeeds when your endpoint responds with a 2xx status within 10 seconds. Anything else (including timeouts) is retried.
- Failed deliveries are retried up to 8 total attempts with increasing backoff: 30s, 2m, 10m, 30m, 2h, 6h, then 12h between attempts. After the final failure the delivery is dead-lettered and visible in the dashboard's delivery logs.
webhook-idis stable across retries of the same event. Process idempotently: record handled IDs and skip duplicates.- Endpoints that fail persistently may be disabled automatically; re-enable them from the dashboard once your receiver is healthy.
- Respond quickly (enqueue work, then return 200) to stay inside the 10-second window.
Last updated June 11, 2026