Webhooks

Verificar assinaturas

Confirme que cada requisição veio do LiveTP e não foi alterada no caminho.

O LiveTP assina os webhooks no formato Standard Webhooks. Toda entrega traz três headers:

HeaderConteúdo
webhook-idID do evento (evt_…), igual ao campo id do corpo.
webhook-timestampSegundos Unix da tentativa atual.
webhook-signatureUma ou mais assinaturas v1,BASE64, separadas por espaço.

As entregas também usam Content-Type: application/json e User-Agent: LiveTP-Webhooks/1.0.

Como a assinatura é calculada

chave      = Base64Decode(segredo sem o prefixo "whsec_")
conteúdo   = webhook-id + "." + webhook-timestamp + "." + corpo_bruto
assinatura = "v1," + Base64(HMAC_SHA256(chave, conteúdo))

Para validar:

  1. Use o corpo bruto, exatamente como recebido. Não reserialize o JSON.
  2. Recuse timestamps com mais de 5 minutos de diferença do seu relógio.
  3. Calcule a assinatura esperada e compare em tempo constante com cada valor do header.
  4. Confira se o id do corpo é igual ao webhook-id.
  5. Deduplique pelo par environment + id antes de processar.

Cada tentativa recebe timestamp e assinatura novos; o ID e o corpo continuam iguais.

Exemplos

verify.ts
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyLiveTPWebhook(
  secret: string,
  headers: Record<string, string | undefined>,
  rawBody: string,
) {
  const id = headers["webhook-id"] ?? "";
  const timestamp = headers["webhook-timestamp"] ?? "";
  const signatures = (headers["webhook-signature"] ?? "").split(" ");

  if (!/^\d{10}$/.test(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const expected = Buffer.from(
    "v1," +
      createHmac("sha256", key)
        .update(`${id}.${timestamp}.${rawBody}`)
        .digest("base64"),
  );
  return signatures.some((value) => {
    const received = Buffer.from(value);
    return received.length === expected.length && timingSafeEqual(received, expected);
  });
}

Bibliotecas compatíveis com Standard Webhooks (como standardwebhooks para Node.js e Python) também funcionam com o segredo whsec_….

Receptores completos

O LiveTP disponibiliza receptores de referência que usam apenas a biblioteca padrão. Eles verificam a assinatura, respondem ao teste, gravam o evento em uma inbox SQLite com chave única e só então confirmam com 202:

Execute-os atrás de um proxy HTTPS e processe a inbox em um consumidor separado, com idempotência.

Respostas recomendadas

SituaçãoResponda
Evento verificado e persistido200 ou 202
Evento de teste verificado200 com {"challenge": "..."}
Assinatura ou timestamp inválidos401
Falha temporária ao persistir503 (o LiveTP tentará de novo)

Responder 401 ou 403 pausa o destino para correção. Use esses códigos só para falhas de autenticação. Veja Entregas e novas tentativas.

Rotação do segredo

Em Integrações → Webhooks, cada destino oferece duas opções:

  • Rotacionar segredo: gera um novo segredo e mantém o anterior válido por 24 horas. Nesse período, cada entrega leva duas assinaturas no header. Atualize o receptor dentro do prazo. Uma nova rotação normal só é permitida depois que o prazo termina.
  • Revogar e trocar segredo: invalida o segredo anterior na hora. Use se o segredo vazou.

Para aceitar os dois segredos durante a transição, verifique contra ambos, como fazem os receptores de referência (que aceitam uma lista separada por vírgulas em LIVETP_WEBHOOK_SECRET).

Verificação sem criptografia local

Plataformas que não permitem calcular HMAC (como alguns ambientes do n8n) podem delegar a verificação ao endpoint Verificar entrega, autenticado pelo token Bearer próprio do destino.