hooksig / Zoom
HMAC-SHA256 · hex · timestamped
Zoom
Zoom signs exactly like Slack: a v0:{ts}:{body} basestring with the timestamp in its own header.
Try it, live in your browser
Request: edit anything
Verify this in code
import { verify } from "hooksig";
const result = await verify("zoom", {
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
x-zm-signature - AlgorithmHMAC-SHA256
- Encodinghex
- Timestampyes, so it has replay protection
- SignsHMAC-SHA256 over `v0:{timestamp}:{body}`, hex, prefixed `v0=`. The timestamp arrives in its own header.
Gotchas
- The signed string is `v0:{timestamp}:{body}` with colons, and the timestamp comes from x-zm-request-timestamp, a separate header.
- Prefix your computed digest with v0= before comparing.
- The key is the app's Secret Token from the Zoom app credentials, verbatim.
- Reject requests whose timestamp is more than five minutes old to stop replays.