Daybook DocsDaybook Docs
Browse

Verifying signatures

Check that every event came from Daybook and wasn't replayed, in Node, Python or Rust.

Daybook signs every webhook the Standard Webhooks way, so any Standard Webhooks library verifies it. Each request carries three headers:

text
webhook-id: evt_01JBXK8N3QZ7V2C4D5E6F7G8HJ
webhook-timestamp: 1761748300
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=

The signature is the base64 HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{raw body}, keyed with your endpoint's secret: the base64 bytes after its whsec_ prefix. To verify:

  1. Base64-decode the secret after its whsec_ prefix; those bytes are the key.
  2. Compute the HMAC over the id, a ., the timestamp, a ., and the raw request body. Parse the JSON only after.
  3. Compare it in constant time with each v1, value in webhook-signature (the header can hold several, separated by spaces).
  4. Reject if webhook-timestamp is more than 5 minutes from now.
import crypto from "node:crypto";

export function verifyDaybookWebhook(rawBody, headers, secret) {
  const id = headers["webhook-id"];
  const timestamp = headers["webhook-timestamp"];
  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const expected = crypto
    .createHmac("sha256", key)
    .update(`${id}.${timestamp}.${rawBody}`)
    .digest();
  const ok = headers["webhook-signature"].split(" ").some((s) => {
    const [version, sig] = s.split(",");
    const got = Buffer.from(sig ?? "", "base64");
    return version === "v1" && got.length === expected.length && crypto.timingSafeEqual(got, expected);
  });
  if (!ok || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    throw new Error("invalid Daybook webhook");
  }
  return JSON.parse(rawBody);
}

Frameworks that parse JSON before your handler runs change the bytes. Read the raw body: express.raw(), request.get_data(), or your framework's equivalent.

Rolling a secret

Open the endpoint in Developers → Webhooks in the Daybook app and choose Roll secret. The new whsec_ secret is shown once and signs every delivery from then on, so deploy it right away, then check the endpoint with Send test event.

On this pageRolling a secret