Browse docs
How to verify a webhook signature#
Each delivery carries a X-SkedCast-Signature header shaped t=<unix-seconds>,v1=<hex>. The signed string is "<t>.<rawBody>" — the timestamp, a literal period, and the EXACT raw request body bytes (not a re-serialized version of the parsed JSON, which can reorder keys or change whitespace and silently break verification). The digest algorithm is HMAC-SHA256, keyed with your endpoint's signing secret (shown once when you create the webhook).
import { createHmac, timingSafeEqual } from "node:crypto";
function verifySkedcastSignature(
secret: string,
rawBody: string, // exact bytes of the request body
header: string, // the X-SkedCast-Signature header value
toleranceSeconds = 300,
): boolean {
const match = /^t=(\d+),v1=([0-9a-f]+)$/.exec(header);
if (match == null) return false;
const [, timestamp, signature] = match;
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - Number(timestamp)) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const expectedBuf = Buffer.from(expected, "utf8");
const providedBuf = Buffer.from(signature, "utf8");
if (expectedBuf.length !== providedBuf.length) return false;
return timingSafeEqual(expectedBuf, providedBuf);
}Two independent schemes, verify either#
Every delivery also carries the Standard Webhooks headers (webhook-id, webhook-timestamp, webhook-signature) alongside X-SkedCast-Signature, so you can use any off-the-shelf Standard Webhooks verification library instead of hand-rolling the HMAC yourself. Both are always present; you only need to verify one.
FAQ
- Can I test my verification code without waiting for a real event?
- Yes — POST /webhooks/:id/test sends a real, signed test delivery to your endpoint on demand, and this site's interactive webhook signature tester tool lets you paste a secret, timestamp, and body to check your own implementation's output against the reference calculation.
- What's the replay-protection window, exactly?
- SkedCast recommends rejecting a signature whose timestamp is more than ~300 seconds (5 minutes) away from your server's current time — tune it if your clocks have unusual drift, but don't skip the check.