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.