Ir para o conteúdo

Webhooks

Como cadastrar endpoints de webhook no Data Lead, quais eventos existem, como verificar a assinatura HMAC em Node e PHP e como usar o log de entrega.

Atualizado em

Os webhooks ficam na tela Integrações, no menu lateral, grupo Conta (endereço /integracoes), na seção Webhooks.

Cada endpoint recebe os eventos escolhidos, assinados com HMAC-SHA256.

Webhooks estão disponíveis nos planos Pro e Full. O formulário nasce desabilitado com "O plano Grátis não inclui os webhooks de saída" e um botão Solicitar upgrade, que abre um e-mail para a Data Stone já com o seu plano atual e o recurso pedido preenchidos — o bloqueio aparece antes de você preencher qualquer campo, não depois de clicar em salvar. Se o plano mudar com a tela aberta, o servidor ainda recusa com "Webhooks estão disponíveis nos planos Pro e Full.". Só o dono e os administradores da conta cadastram endpoints.

Seção Webhooks com um endpoint cadastrado e o log de entrega abaixo

Cadastrar um endpoint

  1. Em URL do endpoint, informe o destino (ex.: https://seusistema.com/webhooks/datalead).
  2. Em Descrição (opcional), um rótulo para você (ex.: ERP interno), até 120 caracteres.
  3. Em Eventos, marque pelo menos um.
  4. Clique em Adicionar endpoint.

O segredo aparece uma única vez

Logo após a criação, a tela mostra:

Copie agora: ele não será exibido de novo.

O segredo começa com whsec_ e é o que você usa para verificar a assinatura de cada entrega. Guarde-o num cofre de segredos: o Data Lead guarda apenas a versão cifrada e não há tela que consiga exibi-lo de novo. Se você perder o segredo, exclua o endpoint e cadastre outro.

URLs recusadas

O cadastro (e cada entrega) recusa destinos que não sejam endpoints públicos de cliente:

Recusa Motivo
URL sem https "URL de webhook precisa usar https"
Porta diferente de 443/80 "URL de webhook precisa usar a porta padrão (443)"
URL com usuário e senha (https://user:senha@host) "URL de webhook não pode carregar usuário e senha"
localhost, .internal e nomes internos "URL de webhook aponta para um host interno"
Endereços de rede privada (10/8, 169.254/16, …) "URL de webhook aponta para um endereço de rede privada"

No formulário, qualquer um desses casos aparece como "Informe uma URL https pública (endereços internos não são aceitos)".

A validação é refeita a cada entrega, inclusive resolvendo o nome: um domínio público que passe a apontar para um endereço interno é recusado na hora do envio. Redirecionamentos não são seguidos — cadastre a URL final.

Os eventos

Evento Rótulo na tela Quando dispara
lead.created Lead criado O lead foi persistido
lead.qualified Lead qualificado O desfecho da conversa virou qualificado
meeting.booked Reunião marcada O agente marcou a reunião
handoff.requested Handoff para WhatsApp O visitante seguiu para o WhatsApp

Um endpoint ativo que assine lead.created ou lead.qualified conta como destino do lead: a partir daí o Data Lead para de mandar o aviso de lead novo por e-mail. Um endpoint que só assina meeting.booked ou handoff.requested não conta — ele não recebe o lead.

O corpo da entrega

Um POST com corpo JSON:

{
  "id": "lead_qualified:5f1c…:webhook:9a2b…",
  "type": "lead.qualified",
  "created_at": "2026-09-05T12:00:00.000Z",
  "data": {
    "leadId": "5f1c8b0e-9d2a-4a11-9a0e-2c6d1f3b7e40",
    "conversationId": "c2b0f6a1-3d4e-4f52-8b77-9e1a5c0d2f31",
    "score": 82,
    "siteId": "1e7a4c33-5b90-4a2f-9c18-6d5e0b7a1234",
    "createdAt": "2026-09-05T11:59:58.412Z"
  }
}

Sem dados pessoais, de propósito: só identificadores, score e site. Quem precisa do contato busca o lead pela API pública — mesmo lá, o contato vem mascarado.

Cabeçalhos de cada entrega:

Cabeçalho Conteúdo
X-DataLead-Signature t=<epoch em segundos>,v1=<HMAC-SHA256 em hexadecimal>
X-DataLead-Id O mesmo id do corpo — guarde e ignore repetidos
X-DataLead-Event O type do evento
User-Agent DataLead-Webhooks/1

Verificar a assinatura

A assinatura é o HMAC-SHA256 do texto <timestamp>.<corpo cru>, com o segredo whsec_… como chave. Ela vale por uma janela de 5 minutos: compare o t com o seu relógio e recuse fora dela — é isso que impede alguém de reenviar uma entrega antiga capturada.

Use o corpo cru. Reserializar o JSON já convertido em objeto muda espaços e ordem de chaves, e a assinatura não fecha.

Node

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

export const verificar = (body, header, secret, toleranciaSegundos = 300) => {
  const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(String(header ?? "").trim());
  if (!match) return false;
  const [, ts, assinatura] = match;
  if (Math.abs(Math.floor(Date.now() / 1000) - Number(ts)) > toleranciaSegundos) return false;
  const esperado = createHmac("sha256", secret).update(`${ts}.${body}`).digest("hex");
  return timingSafeEqual(Buffer.from(esperado, "hex"), Buffer.from(assinatura, "hex"));
};

// Express:
// app.post("/hook", express.raw({ type: "application/json" }), (req, res) => {
//   const corpo = req.body.toString("utf8");
//   if (!verificar(corpo, req.get("X-DataLead-Signature"), process.env.DATALEAD_WEBHOOK_SECRET)) {
//     return res.sendStatus(400);
//   }
//   const evento = JSON.parse(corpo);
//   // Idempotência: guarde req.get("X-DataLead-Id") e ignore repetidos.
//   res.sendStatus(204);
// });

PHP

<?php
function datalead_verificar(string $body, ?string $header, string $secret, int $tolerancia = 300): bool {
    if ($header === null || !preg_match('/^t=(\d+),v1=([0-9a-f]{64})$/', trim($header), $m)) {
        return false;
    }
    [, $ts, $assinatura] = $m;
    if (abs(time() - (int) $ts) > $tolerancia) {
        return false;
    }
    $esperado = hash_hmac('sha256', $ts . '.' . $body, $secret);
    return hash_equals($esperado, $assinatura);
}

$body = file_get_contents('php://input');            // corpo CRU, sem json_decode antes
$header = $_SERVER['HTTP_X_DATALEAD_SIGNATURE'] ?? null;

if (!datalead_verificar($body, $header, getenv('DATALEAD_WEBHOOK_SECRET'))) {
    http_response_code(400);
    exit;
}

$evento = json_decode($body, true);
// Idempotência: guarde $_SERVER['HTTP_X_DATALEAD_ID'] e ignore repetidos.
http_response_code(204);

hash_equals (PHP) e timingSafeEqual (Node) comparam em tempo constante. Uma comparação com == ou === vazaria, byte a byte, quanto da assinatura um atacante já acertou.

Responder à entrega

Responda com qualquer status 2xx — o corpo da sua resposta é ignorado. Cada tentativa espera no máximo 5 segundos pela sua resposta; acima disso a entrega é marcada como tempo limite.

Nem toda falha é repetida. A fila só insiste quando o problema parece temporário — 5xx, 429 ou tempo limite: espera crescente (1 min, 2, 4, 8… até 24 h), somando 14 tentativas ao longo de cerca de 82 horas. Um 4xx (401, 403, 404 ou qualquer outro erro de requisição) falha na primeira tentativa: é um problema do seu lado que repetir não resolve. Nos dois casos a linha fica como Falhou no log e pode ser reenviada manualmente.

Dez falhas consecutivas desativam o endpoint (contagem por endpoint, independente das tentativas de cada entrega) e o dono da conta recebe um e-mail com o assunto "Um webhook do Data Lead parou de responder". Conserte o destino e clique em Ativar.

Testar, ativar e excluir

Cada endpoint da lista traz:

  • Enviar teste — enfileira um evento lead.created de teste, com "teste": true no data. O aviso é "Enviamos um evento de teste. O resultado aparece no log em alguns segundos." O endpoint precisa estar ativo; desativado, a tela diz "Ative o endpoint para testar."
  • Desativar / Ativar — para de receber eventos sem perder o cadastro nem o histórico.
  • Excluir — definitivo: "Os eventos param de ser enviados para esta URL na hora, e não há como desfazer."

Quando há falhas, a linha mostra "3 falha(s) consecutiva(s) — último HTTP 500."

Log de entrega

O bloco Log de entrega lista uma linha por tentativa, da mais recente para a mais antiga: Quando, Tipo, Tentativa, Status (Entregue ou Falhou), HTTP, Duração e Erro.

O botão Reenviar aparece apenas na tentativa mais recente de uma entrega que falhou de vez. Ele reabre a mesma entrega, zerando as tentativas: "Reenviado. O resultado aparece no log em instantes."

Se a listagem falhar, a tela avisa explicitamente que as entregas continuam acontecendo — só a listagem falhou.

Um destino recusado pela política de rede aparece no log sem status HTTP e com a mensagem genérica: informar o endereço resolvido transformaria o log numa ferramenta de varredura de rede.

Log de entrega com uma linha "Entregue" e uma "Falhou" com o botão Reenviar

Veja também

Precisa de ajuda?

Fale com o suporte da Data Stone pelo canal combinado com a sua conta. Tenha à mão a URL do endpoint e o X-DataLead-Id da entrega (nunca envie o segredo whsec_…).