Developer Portal

Verificar assinatura de webhook

Raw body, HMAC-SHA256, rotação de secret e proteção contra replay.

Atualizado em 01/09/2026

O Hubday segue Standard Webhooks. A entrada assinada é webhook-id + '.' + webhook-timestamp + '.' + rawBody. O secret começa com whsec_ e o material após o prefixo é Base64. Compare a assinatura HMAC-SHA256 em tempo constante.

import { createHmac, timingSafeEqual } from "node:crypto";

function verifyHubdayWebhook(input: {
  rawBody: string;
  webhookId: string;
  timestamp: string;
  signatureHeader: string;
  secret: string;
}) {
  const timestamp = Number(input.timestamp);
  if (!Number.isInteger(timestamp)) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > 5 * 60) return false;

  const key = Buffer.from(input.secret.replace(/^whsec_/, ""), "base64");
  const signed = `${input.webhookId}.${timestamp}.${input.rawBody}`;
  const expected = createHmac("sha256", key).update(signed, "utf8").digest();

  return input.signatureHeader.split(" ").some((candidate) => {
    const [version, encoded] = candidate.split(",", 2);
    if (version !== "v1" || !encoded) return false;
    const actual = Buffer.from(encoded, "base64");
    return actual.length === expected.length && timingSafeEqual(actual, expected);
  });
}
Importante: Use o body exatamente como chegou na rede. JSON.parse + JSON.stringify pode alterar bytes e invalidar a assinatura. Também aplique uma tolerância de timestamp e deduplique pelo webhook-id para reduzir replay.