How do I check a webhook really came from Galley?
Every message has a Galley-Signature header like t=1792230600,v1=5257a869…. t is when it was sent, in seconds since 1970. v1 is an HMAC-SHA256, in hex, of t, a full stop and the body, made with your endpoint's signing secret. To check it:
- Make the same HMAC with your whole secret (
whsec_included) overt, a full stop and the body exactly as it arrived, before any JSON parsing. - Compare it with
v1, in constant time. - Refuse a message whose
tis more than 5 minutes away from now, so an old one can't be sent again.
In Node.js:
import crypto from "node:crypto";
function isFromGalley(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!Number.isInteger(t) || !parts.v1 || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(t + "." + rawBody).digest("hex");
return expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
Keep the secret in your server's settings, not in its code. If it leaks, delete the endpoint and add it again to get a new one.