Símbolo da AmivuSímbolo da AmivuDevelopers

Assinaturas

Como conferir que a entrega veio de nós — em Node, Python e PHP.

Qualquer pessoa pode fazer um POST no seu endpoint. A assinatura é o que distingue uma entrega da Amivu de um pedido inventado — e conferi-la não é opcional.

Como a assinatura é feita

A Amivu calcula um HMAC-SHA-256 sobre <timestamp>.<corpo>, usando o segredo do seu endpoint, e manda o resultado no cabeçalho:

X-Amivu-Signature: t=1756240000,v1=5257a869e7ecebeda32affa62cdca3fa...
  • t — o instante da assinatura, em segundos.
  • v1 — o esquema. Um dia haverá v2 ao lado, e quem confere v1 continuará funcionando durante a transição.

Como conferir

Use o corpo BRUTO
Analisar o JSON e serializar de novo muda espaços e ordem de chaves — e a assinatura deixa de bater. Guarde os bytes exatos que chegaram antes de qualquer JSON.parse. É o erro que mais aparece nesta etapa.
import crypto from "node:crypto";

const TOLERANCIA_EM_SEGUNDOS = 300;

export function assinaturaValida(cabecalho, corpoBruto, segredo) {
  // 1. separe o timestamp da assinatura
  const partes = Object.fromEntries(
    cabecalho.split(",").map((parte) => parte.trim().split("=", 2)),
  );
  const timestamp = Number.parseInt(partes.t, 10);
  if (!Number.isFinite(timestamp) || !partes.v1) return false;

  // 2. recuse o que é velho demais — é isto que impede repetição
  const agora = Math.floor(Date.now() / 1000);
  if (Math.abs(agora - timestamp) > TOLERANCIA_EM_SEGUNDOS) return false;

  // 3. refaça a conta com o corpo BRUTO
  const esperado = crypto
    .createHmac("sha256", segredo)
    .update(`${timestamp}.${corpoBruto}`, "utf8")
    .digest();

  const recebido = Buffer.from(partes.v1, "hex");
  if (recebido.length !== esperado.length) return false;

  // 4. compare em tempo constante
  return crypto.timingSafeEqual(recebido, esperado);
}

Como pegar o corpo bruto varia por framework. No Express, use express.raw({ type: "application/json" }) na rota do webhook — o express.json() global já teria consumido e reserializado o corpo.

O timestamp mata a repetição

Assinar só o corpo deixaria uma entrega capturada válida para sempre: bastaria reenviá-la. Com o timestamp dentro da conta, a cópia continua com assinatura correta e instante velho — e a janela de cinco minutos a recusa.

Um verificador que ignora o `t` não está protegido
Ele compara o HMAC direito e aceita uma entrega de ontem. Os três exemplos acima checam a janela antes de calcular qualquer coisa, e é essa a ordem certa.

Rotacionar o segredo

No painel, em Desenvolvedores → Webhooks, o botão Rotacionar gera outro segredo. A entrega seguinte já assina com o novo — sem janela de convivência.

É deliberado: um segredo com dois valores válidos ao mesmo tempo continua valendo depois de ter sido rotacionado, que é o oposto do motivo de rotacionar. Atualize os dois lados no mesmo minuto, e prefira fazê-lo em horário de baixo movimento.

Continue em Reentregas.

Escrevemos sobre operação de atendimento, canais e IA no blog da Amivu.