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:
| Header | Conteúdo |
|---|---|
webhook-id | ID do evento (evt_…), igual ao campo id do corpo. |
webhook-timestamp | Segundos Unix da tentativa atual. |
webhook-signature | Uma 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:
- Use o corpo bruto, exatamente como recebido. Não reserialize o JSON.
- Recuse timestamps com mais de 5 minutos de diferença do seu relógio.
- Calcule a assinatura esperada e compare em tempo constante com cada valor do header.
- Confira se o
iddo corpo é igual aowebhook-id. - Deduplique pelo par
environment+idantes de processar.
Cada tentativa recebe timestamp e assinatura novos; o ID e o corpo continuam iguais.
Exemplos
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:
- Receptor Node.js (Node.js 24+)
- Receptor Python (Python 3)
Execute-os atrás de um proxy HTTPS e processe a inbox em um consumidor separado, com idempotência.
Respostas recomendadas
| Situação | Responda |
|---|---|
| Evento verificado e persistido | 200 ou 202 |
| Evento de teste verificado | 200 com {"challenge": "..."} |
| Assinatura ou timestamp inválidos | 401 |
| Falha temporária ao persistir | 503 (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.