Visão geral
Um webhook é um endereço do seu sistema que recebe um aviso a cada evento que você escolheu. O painel manda um POST com um JSON, assinado com o segredo daquele webhook. Se o seu sistema não responder, tentamos de novo sozinhos, e cada tentativa fica no registro de entregas.
- Evento no painel
- Aviso assinado
- Seu sistema
- Registro de entregas
Crie o webhook
No painel, em Integrações → Webhooks → Novo webhook: o endereço (https) e os eventos, em grupos.
Guarde o segredo
O segredo de assinatura (whsec_…) aparece uma vez, quando o webhook é criado. É com ele que o seu sistema confere cada aviso.
Mande um teste
O botão Testar do cartão manda um exemplo com dados fictícios. O resultado aparece no próprio cartão.
Receber um aviso
O aviso chega como POST no endereço que você cadastrou, com o corpo em JSON (UTF-8). Responda com qualquer código 2xx em até 5 segundos: é o que conta como entregue. Se o processamento do seu lado demora, responda primeiro e processe depois.
- Só endereços https, na porta padrão, na internet. Endereço de rede interna é bloqueado.
- Não seguimos redirecionamento: cadastre o endereço final.
- Guardamos até 500 caracteres do começo da sua resposta no registro de entregas. Uma mensagem curta de erro ajuda você mesmo a descobrir o que houve.
Conferir a assinatura
Qualquer um que descubra o endereço pode mandar um POST para ele. A assinatura prova que o aviso saiu do painel e não foi mexido no caminho.
- Leia os cabeçalhos X-Webhook-Timestamp e X-Webhook-Signature-V2.
- Calcule o HMAC SHA-256 de timestamp.corpo (o carimbo, um ponto e o corpo cru, exatamente como chegou) com o segredo de assinatura, em hexadecimal, e compare com o que vem depois de sha256=.
- Recuse avisos com mais de 5 minutos e use X-Webhook-Id para não processar o mesmo aviso duas vezes.
import crypto from "node:crypto"; // corpo: o texto cru da requisição, exatamente como chegou (não o JSON já lido). function avisoValido(corpo, cabecalhos, segredo) { const ts = Number(cabecalhos["x-webhook-timestamp"]); if (!ts || Math.abs(Date.now() / 1000 - ts) > 300) return false; const esperada = "sha256=" + crypto.createHmac("sha256", segredo).update(`${ts}.${corpo}`).digest("hex"); const recebida = cabecalhos["x-webhook-signature-v2"] ?? ""; return ( esperada.length === recebida.length && crypto.timingSafeEqual(Buffer.from(esperada), Buffer.from(recebida)) ); }
A assinatura antiga, X-Webhook-Signature (HMAC SHA-256 só do corpo), continua saindo, para quem já integrou. Para integração nova, use a V2: ela leva o carimbo de hora, e um aviso copiado não pode ser repetido depois.
Perdeu o segredo? Em Editar, Gerar segredo novo troca na hora: o anterior para de valer, e o seu sistema precisa ser atualizado logo depois.
Envelope
Todo aviso começa com os mesmos campos. Depois deles vêm os campos do evento.
| Campo | O que é |
|---|---|
| event | A chave do evento, como lead.qualified. É a mesma do cabeçalho X-Webhook-Event. |
| event_id | O ID único do evento (uuid). O reenvio usa o mesmo: é por ele que o seu sistema não duplica. |
| occurred_at | Quando o evento aconteceu no painel, em UTC com Z (2026-09-24T17:02:10.917204Z). Use para ordenar. |
| timestamp | Quando o corpo foi montado, em UTC no formato de sempre (2026-09-24T17:02:11.482913+00:00). |
| lead_id | O ID do lead no painel (menos em order.imported, que é um lote). |
| external_id | O ID do lead no seu sistema, quando ele veio por uma integração de entrada. Senão, null. |
| test | true no evento de teste. Não vem nos avisos de verdade. |
Os 13 eventos
Cada webhook escolhe os eventos que quer receber. Os avisos saem sem dados pessoais, como nos exemplos abaixo; veja .
Leads
| Evento | Quando sai | Campos |
|---|---|---|
| lead.created Lead criado | Chegou um lead novo por conversa, integração, cadastro manual, comentário ou story. Não sai em importação por planilha nem para lead de teste. |
|
| lead.qualified Lead qualificado | A IA qualificou o lead, ou alguém levou o cartão para a coluna de qualificado. Sai uma vez por lead: reaberto e qualificado de novo, ele não sai outra vez. |
|
| lead.hot Lead quente | O lead virou quente. Não sai de novo enquanto ele continuar quente. |
|
| lead.opted_out Pediu para não receber mensagens | O lead pediu para parar de receber mensagens. Se outra ferramenta sua dispara mensagens, avise-a no mesmo minuto. |
|
| lead.anonymized Dados apagados (LGPD) | Os dados do lead foram apagados a pedido do titular. O external_id é lido antes de apagar, para o seu sistema achar a cópia dele e apagar também. |
|
{ "event": "lead.created", "event_id": "5b1e2c7a-6a1f-4c1e-9a0e-3f2d9b7c1a10", "timestamp": "2026-09-24T17:02:11.482913+00:00", "occurred_at": "2026-09-24T17:02:10.917204Z", "lead_id": "7f0c2a9e-1b3d-4e8f-9a01-6c5d4e3f2a1b", "external_id": "DL-88213", "source": "integracao", "integration": "Distribuidor — anúncios", "origin": { "platform": "meta", "campaign_name": "Lançamento Alcântara", "adset_name": "Alcântara 25-40", "ad_name": "Vídeo varanda", "ad_id": "120210000000000003" }, "created_at": "2026-09-24T17:02:10.917204Z" }
Funil
| Evento | Quando sai | Campos |
|---|---|---|
| lead.stage_changed Mudou de etapa | O cartão mudou de coluna no Funil: pela IA, por uma pessoa, pelo desfecho, pela entrada de leads ou pela API. |
|
| lead.won Venda (ganho) | O negócio foi marcado como ganho. Antes dele sai o lead.stage_changed da ida para a coluna de ganho, com by: "desfecho". |
|
| lead.lost Perdido | O negócio foi marcado como perdido, com o motivo. Antes dele sai o lead.stage_changed da ida para a coluna de perdido. |
|
| lead.reopened Negócio reaberto | Um negócio ganho ou perdido voltou para o funil. |
|
{ "event": "lead.stage_changed", "event_id": "5b1e2c7a-6a1f-4c1e-9a0e-3f2d9b7c1a10", "timestamp": "2026-09-24T17:02:11.482913+00:00", "occurred_at": "2026-09-24T17:02:10.917204Z", "lead_id": "7f0c2a9e-1b3d-4e8f-9a01-6c5d4e3f2a1b", "external_id": "DL-88213", "board": { "id": "b1c8e0d4-2f6a-4e1b-9c3d-5a7e8f9b0c1d", "name": "Imobiliária" }, "from": { "column_id": "c4a19f3e-7b2d-4c8a-9e1f-3d5b7a9c1e2f", "key": "visita", "name": "Visita", "role": "intermediaria" }, "to": { "column_id": "c5b20e4f-8c3e-4d9b-af20-4e6c8b0d2f3a", "key": "pasta", "name": "Pasta", "role": "intermediaria" }, "by": "pessoa", "custom_fields": { "valor_imovel": 45000000, "tipo_financiamento": "Financiamento bancário" } }
Atendimento
| Evento | Quando sai | Campos |
|---|---|---|
| lead.assigned Atribuído a um atendente | O lead foi para um atendente: pelo rodízio, à mão, pela coluna do Funil ou porque o prazo para responder estourou. |
|
| conversation.handoff Transbordo para humano | A conversa passou da IA para uma pessoa. No transbordo automático com rodízio chegam dois avisos: um com reason: "ia" (a IA pediu uma pessoa) e outro com reason: "atribuicao" (quem recebeu), cada um com o seu event_id. |
|
| lead.first_contact Primeiro contato | Saiu o resultado da primeira mensagem automática da entrada de leads. |
|
{ "event": "lead.assigned", "event_id": "5b1e2c7a-6a1f-4c1e-9a0e-3f2d9b7c1a10", "timestamp": "2026-09-24T17:02:11.482913+00:00", "occurred_at": "2026-09-24T17:02:10.917204Z", "lead_id": "7f0c2a9e-1b3d-4e8f-9a01-6c5d4e3f2a1b", "external_id": "DL-88213", "conversation_id": "e4f5a6b7-c8d9-4e0f-a1b2-c3d4e5f6a7b8", "attendant": { "id": "a7b8c9d0-e1f2-4a3b-8c4d-5e6f7a8b9c0d", "name": "Juliana Prado", "email": "juliana@exemplo.com" }, "reason": "rodizio" }
Pedidos
| Evento | Quando sai | Campos |
|---|---|---|
| order.imported Pedidos importados | Chegaram pedidos pela API de pedidos ou por planilha. Não fala de um lead: é o resumo do lote. |
|
{ "event": "order.imported", "event_id": "5b1e2c7a-6a1f-4c1e-9a0e-3f2d9b7c1a10", "timestamp": "2026-09-24T17:02:11.482913+00:00", "occurred_at": "2026-09-24T17:02:10.917204Z", "source": "api", "imported": 12, "updated": 2, "total": 14 }
Dados pessoais
Os avisos levam identificadores: o ID do lead, a etapa, a nota, os valores e os campos do negócio que não são sensíveis (campo de texto livre leva o que o time escreveu nele). Nome, telefone, e-mail e o resumo da conversa não saem, e campo sensível nunca sai.
- Quando o titular pede para apagar os dados, sai o lead.anonymized: apague do seu sistema o que você guardou sobre ele.
Cabeçalhos
Content-Type: application/json User-Agent: Webhooks/1.0 X-Webhook-Event: lead.qualified X-Webhook-Id: 5b1e2c7a-6a1f-4c1e-9a0e-3f2d9b7c1a10 X-Webhook-Timestamp: 1790269331 X-Webhook-Signature: sha256=3f9a0c1b7e2d… X-Webhook-Signature-V2: sha256=7d0c9a41e5b8…
| Cabeçalho | O que é |
|---|---|
| X-Webhook-Event | A chave do evento. |
| X-Webhook-Id | O event_id. Igual no reenvio. |
| X-Webhook-Timestamp | O carimbo de hora da assinatura, em segundos (Unix). |
| X-Webhook-Signature-V2 | sha256= e o HMAC de timestamp.corpo. É a que recomendamos conferir. |
| X-Webhook-Signature | sha256= e o HMAC só do corpo. A assinatura antiga, que continua saindo. |
| User-Agent | Webhooks/1.0. |
Até 09/02/2027, os avisos também levam X-AttendTo-Event e X-AttendTo-Signature, com os mesmos valores. Troque para os X-Webhook-* antes disso.
Novas tentativas
Resposta 2xx conta como entregue. Sem resposta em 5 s, erro no seu servidor (5xx), recusa (4xx) ou certificado com problema: tentamos de novo nestes intervalos, até 6 tentativas.
- 1Na hora
- 2+1 min
- 3+5 min
- 4+30 min
- 5+2 h
- 6+6 h
- Paramos
- Uma resposta 410 faz a gente parar na hora: é o jeito de o seu sistema dizer que aquele endereço não existe mais.
- Endereço que aponta para uma rede interna é bloqueado e não é tentado de novo.
- Depois da 6ª tentativa, a entrega fica como Não entregue no registro, com o botão Reenviar.
Pausa automática
Depois de 50 avisos seguidos que esgotaram as tentativas, pausamos o webhook e avisamos o dono da conta por e-mail e no sino. Nada se perde: as entregas que ficaram de fora aparecem no registro, e os avisos do período em que ele ficou pausado também, como Não entregue. Corrija o endereço no sistema que recebe, clique em Reativar e reenvie as falhas.
Ordem e duplicidade
Os avisos podem chegar fora de ordem quando há novas tentativas, e o mesmo aviso pode chegar mais de uma vez (o reenvio, por exemplo). Então:
- Ordene pelo campo occurred_at, e não pela hora em que o aviso chegou.
- Guarde os event_id já processados e ignore um que chegar de novo.
- No transbordo automático com rodízio chegam dois conversation.handoff: um com reason: "ia" e outro com reason: "atribuicao", junto do lead.assigned. São eventos diferentes, com event_id diferentes.
- Na venda e na perda, primeiro sai o lead.stage_changed com by: "desfecho" e depois o lead.won ou o lead.lost.
Evento de teste
O botão Testar do cartão manda o evento que você escolher com dados fictícios (“Lead de teste”), a marca "test": true e um event_id novo a cada teste. É uma tentativa só, sem nova tentativa, e ela aparece no registro de entregas. O teste não conta para a pausa automática.
Registro de entregas
Em Integrações → Entregas fica tudo o que saiu nos últimos 30 dias. Abra uma entrega para ver o corpo enviado, o começo da resposta, os cabeçalhos e cada tentativa. Reenviar manda de novo com o mesmo event_id, para o seu sistema não duplicar. Depois de 30 dias, as entregas são apagadas sozinhas.
Esta página ajudou?
Precisa de ajuda no primeiro webhook?
Fale com a gente pelo chat do painel. A nossa equipe acompanha você até o primeiro aviso chegar.
Abrir Webhooks no painel