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:

  1. Make the same HMAC with your whole secret (whsec_ included) over t, a full stop and the body exactly as it arrived, before any JSON parsing.
  2. Compare it with v1, in constant time.
  3. Refuse a message whose t is 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.

Still stuck? Write to hello@1936ai.com and a person will answer.