CONTRATO.CLOUD / KYC

Valide a assinatura HMAC dos webhooks

Valide timestamp e HMAC SHA-256 sobre os bytes originais, com exemplos em TypeScript e Python e comparação segura.

Nesta página

Preserve os bytes antes de interpretar JSON

A assinatura usa exatamente timestamp + '.' + raw_body, com HMAC SHA-256 e o segredo de webhook do seu ambiente. Leia o timestamp de X-Face-Auth-Timestamp e a assinatura de X-Face-Auth-Signature, no formato sha256=<hex>.

Não use JSON.stringify ou outra serialização do objeto recebido para reconstruir a mensagem. Espaços, quebras de linha ou ordem de propriedades alteram os bytes. Configure seu framework para disponibilizar o corpo bruto antes do parser JSON.

Verificação no backend

O exemplo recebe uma tolerância temporal definida pela sua política; ela não é uma promessa de prazo de entrega. Mantenha o relógio do servidor sincronizado e avalie reentregas junto à política de replay.

import { createHmac, timingSafeEqual } from "node:crypto";
// Read X-Face-Auth-Timestamp / X-Face-Auth-Signature. rawBody is the exact received Buffer.
export function verifyWebhook(rawBody, timestamp, signature, secret, maxAgeSeconds, nowSeconds = Date.now() / 1000) {
  if (!Buffer.isBuffer(rawBody) || !secret || !Number.isFinite(maxAgeSeconds) || maxAgeSeconds <= 0 || !Number.isFinite(nowSeconds)) return false;
  if (typeof timestamp !== "string" || !/^[0-9]{1,16}$/.test(timestamp) || typeof signature !== "string" || !/^sha256=[a-f0-9]{64}$/.test(signature)) return false;
  const sentAt = Number(timestamp);
  if (!Number.isSafeInteger(sentAt) || Math.abs(nowSeconds - sentAt) > maxAgeSeconds) return false;
  const expected = createHmac("sha256", secret).update(Buffer.from(timestamp + ".", "utf8")).update(rawBody).digest();
  const received = Buffer.from(signature.slice(7), "hex");
  return expected.length === received.length && timingSafeEqual(expected, received);
}
// Only after signature verification: parse and validate the envelope, compare X-Face-Auth-Event-Id with event_id, and durably accept with a unique event_id (atomic insert/queue). A duplicate event_id is acknowledged without repeating effects. Do not deduplicate transaction_id. GET the transaction to reconcile current status, decision and decision_reason; delivery order is not authoritative. Acknowledge only after durable acceptance. Persistence, queueing, body limits and business effects belong to your application; this function only verifies HMAC. maxAgeSeconds is YOUR replay policy, not a service delivery promise. Never reserialize JSON before verification or log secrets/payloads.

Depois da validação

Somente após validar o HMAC, interprete JSON, valide o envelope e processe o evento. Confira o identificador de evento do header e do corpo. Deduplicate por event_id em armazenamento durável; a verificação criptográfica sozinha não impede repetir o mesmo efeito de negócio.

Os exemplos isolam a verificação e não implementam um receptor completo. Adicione limites de corpo, tratamento de exceções, persistência, fila, proteção de logs e reconciliação. Nunca registre o segredo, o corpo com dados pessoais ou links de jornada em ferramentas públicas.

Baixar Markdown

Ambiente protegido