Assinatura

Valide o HMAC de cada entrega antes de confiar nela.

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 t e v1 do header.
  • Rejeite entregas com t muito 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),
  );
}