Assinatura
Valide o HMAC de cada entrega antes de confiar nela.
O header de assinatura#
Toda entrega leva o header Kyvo-Signature no formato t=<unix>,v1=<hmac_sha256>.
O HMAC é calculado sobre a string "{t}.{corpo_bruto}" usando o segredo do endpoint (whsec_…), exibido uma única vez no cadastro.
Passos da validação#
- Leia o corpo bruto, antes de qualquer parse. Reserializar o JSON muda os bytes e quebra a assinatura.
- Separe
tev1do header. - Rejeite entregas com
tmuito antigo — cinco minutos é uma janela razoável contra replay. - Recalcule o HMAC e compare em tempo constante.
Comparação em tempo constante
Não use
=== para comparar assinaturas. Use timingSafeEqual, hmac.compare_digest ou hash_equals.Rotação de segredo
Ao rotacionar o segredo, o novo valor passa a valer imediatamente. Faça a troca em uma janela de baixo movimento ou aceite os dois segredos durante o deploy.
validação
import crypto from "node:crypto";
export function verify(rawBody, header, secret) {
const partes = Object.fromEntries(
header.split(",").map((p) => p.split("=")),
);
const t = partes.t;
const v1 = partes.v1;
// rejeite entregas antigas
const idade = Math.abs(Date.now() / 1000 - Number(t));
if (idade > 300) return false;
const esperado = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(esperado),
Buffer.from(v1),
);
}