hooksig / Calendly
HMAC-SHA256 · hex · timestamped
Calendly
Calendly signs like Stripe: a {t}.{body} payload with t and v1 in one header.
Try it, live in your browser
Request: edit anything
Verify this in code
import { verify } from "hooksig";
const result = await verify("calendly", {
payload: rawBody, // the RAW body, not parsed JSON
headers: request.headers,
secret: env.WEBHOOK_SECRET,
});
if (!result.verified) {
return new Response(result.reason, { status: 400 });
}no match: signature invalid
Signed string
HMAC-SHA256( secret, signed string ) = expected
Expected vs provided
expected
provided
How it's built
The scheme
- Header
Calendly-Webhook-Signature - AlgorithmHMAC-SHA256
- Encodinghex
- Timestampyes, so it has replay protection
- SignsHMAC-SHA256 over `{timestamp}.{body}`, hex, keyed by the webhook signing key verbatim.
Gotchas
- Parse the header into t and v1, then build the signed payload as `{t}.{body}`, not just the body.
- The key is the signing key returned when you created the webhook subscription, used verbatim.
- Sign the RAW body. Re-stringified JSON changes bytes and every check fails.
- Reject if t is outside your tolerance (default 5 minutes) to stop replays.