Central de ajudaAdminQuem decide
Webhooks de saída: enviar eventos aos seus sistemas
POSTs HTTPS assinados para leads novos e mudança de fase do funil. Catálogo completo, payloads, assinaturas e receita de configuração.
Webhooks de saída fazem o Concierge enviar POSTs JSON assinados para o seu endpoint HTTPS (por exemplo um painel comercial, automação de planilha ou CRM interno). A configuração é por workspace em Acesso à API → Webhooks. A entrega é best-effort com retentativas e um log no painel. Captura de lead e chat seguem em paralelo enquanto a entrega roda em segundo plano.
Quando usar webhooks
- Espelhar leads novos em um painel ou planilha quase em tempo real
- Sincronizar mudanças de fase do quadro CRM com um sistema externo
- Alertar o seu backend quando o visitante pede um humano
- Opcional: acompanhar status de entrega de mensagens de disparo
Configurar no painel
- Abrir Acesso à API → Webhooks
É preciso acesso Admin ou Desenvolvedor (permissão de gerir). A página também mostra segredo de assinatura, teste e entregas recentes.
- Colar um endpoint HTTPS
Só URLs HTTPS públicas são aceitas. Redes privadas, HTTP e redirecionamentos são recusados (proteção SSRF). A porta 443 é o caso normal.
- Escolher eventos com critério
O padrão de uma config nova é lead.captured + handoff.requested. Para funil externo, prefira lead.captured + crm.card_moved e deixe o resto desmarcado.
- Ativar, salvar e enviar teste
Ao salvar, um segredo (czwh_…) é gerado se ainda não existir. Use Enviar evento de teste. Envia type ping e ignora a lista de eventos para validar conectividade.
- Conferir Entregas recentes
Uma linha de sucesso com HTTP 2xx significa que o endpoint aceitou o corpo. Corrija erros 4xx de contrato antes do tráfego de produção.
- O endpoint responde 2xx rápido (menos de ~10 segundos por tentativa)
- Você verifica X-Concierge-Signature em toda requisição
- Você ignora tipos de evento desconhecidos para o catálogo poder crescer com segurança
- Você guarda o id do evento para idempotência (pode haver reenvio)
O que a maioria deve assinar
- lead.captured: o assistente capturou contato (widget/canal). Faça upsert de contato/linha.
- crm.card_moved: card do quadro mudou de etapa (fase do funil). Use stageId para sincronizar o funil no painel externo.
Assine depois só se precisar
- handoff.requested: visitante pediu pessoa
- handoff.claimed / returned / closed / requeued: ciclo da mesa
- crm.session_promoted: conversa virou lead no quadro
- crm.workspace_changed: mudanças grosseiras de estrutura do board
- dispatch.accepted / sent / delivered / read / failed / expired: recibos de envio
Formato da requisição HTTP
Cada entrega é um POST JSON. Os cabeçalhos trazem tipo, id e assinatura. O corpo é um envelope estável; só data muda por tipo de evento.
POST /seu-hook
Content-Type: application/json
X-Concierge-Event: lead.captured
X-Concierge-Id: evt_1a2b3c4d5e6f
X-Concierge-Signature: t=1721051696,v1=<hmac_hex>{
"id": "evt_1a2b3c4d5e6f",
"type": "lead.captured",
"createdAt": "2026-07-15T12:34:56.789Z",
"workspaceId": "acme-1a2b",
"data": { }
}- id: id único do evento (use para dedupe se houver reenvio)
- type: mesma string de X-Concierge-Event
- createdAt: timestamp ISO em que o Concierge montou o envelope
- workspaceId: id do tenant / workspace
- data: objeto específico do evento (abaixo)
Verificar a assinatura
A assinatura é HMAC-SHA256 sobre a string `${unix_timestamp}.${rawBody}` com o segredo do workspace. O cabeçalho tem o formato t=<segundos>,v1=<hex>. Compare v1 em tempo constante. Rejeite se t estiver longe do seu relógio (ex.: mais de cinco minutos) para limitar replay.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(",").map((p) => {
const [k, v] = p.split("=");
return [k, v];
}),
);
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return timingSafeEqual(
Buffer.from(parts.v1, "utf8"),
Buffer.from(expected, "utf8"),
);
}Payloads por evento
lead.captured
Dispara quando o assistente captura contato. source costuma ser widget ou um canal de mensagem. Campos podem ser null se o visitante só informou um canal.
{
"lead": {
"id": "lead_9f8e7d",
"name": "Jane Doe",
"email": "jane@example.com",
"phone": null,
"otherContact": null,
"reason": "Quer uma demo",
"preferredChannel": null,
"source": "widget"
}
}crm.card_moved (fase do pipeline)
Dispara quando um card do quadro CRM muda de etapa. Inclui o contato do lead e stageName (o nome da coluna no kanban, o mesmo texto que o time vê). stageId é o id interno estável (por exemplo in-progress). lead é null quando o card ainda é só uma sessão; actor pode ser null quando a mudança for do sistema.
{
"card": {
"id": "card_abc123",
"sessionId": "cwgt:…",
"stageId": "stage_negotiation",
"stageName": "Em andamento",
"revision": 12
},
"lead": {
"id": "lead_9f8e7d",
"name": "Jane Doe",
"email": "jane@example.com",
"phone": "+5511999887766",
"otherContact": null
},
"actor": { "id": "user_…", "name": "Daniel" }
}handoff.requested
{
"handoff": {
"id": "ho_…",
"reason": "Visitante pediu uma pessoa",
"conversationTitle": "Chat do widget"
}
}Ciclo de handoff (claimed / returned / closed / requeued)
{
"handoff": {
"id": "ho_…",
"conversationId": "conv_…"
},
"actor": { "id": "user_…", "name": "Erika" }
}crm.session_promoted
{
"session": { "id": "sess_…" },
"leadId": "lead_…"
}crm.workspace_changed
Sinal grosso de que a estrutura do board ou estado compartilhado mudou (action + workspaceSequence). Prefira card_moved para sincronizar fase; use isto só se reconstruir caches.
dispatch.* (entrega de envio)
{
"dispatch": {
"id": "dsp_…",
"providerMessageId": "wamid.…",
"channelId": "ch_…",
"conversationId": "conv_…",
"recipient": "+55…",
"status": "delivered"
}
}Em falha, pode vir um campo error. Status: accepted, sent, delivered, read, failed, expired. Só progresso real de entrega gera novo evento.
ping (somente teste)
{
"message": "This is a test event from Concierge. Your webhook endpoint is reachable."
}Entrega, retentativas e confiabilidade
- Até 3 tentativas com backoff (cerca de 2s e depois 8s)
- Timeout de cerca de 10 segundos por tentativa
- Respostas 4xx (exceto 408/429) param cedo; corrija o contrato
- Erros permanentes de guard (URL inválida, host bloqueado, não HTTPS) não retentam
- Ações de produto bem-sucedidas completam de forma independente do seu webhook
- Entregas recentes no painel mostram status, código HTTP, tentativas e erro
Checklist de segurança
- Use somente HTTPS no receptor
- Verifique a assinatura em toda requisição antes de confiar nos dados
- Rejeite timestamps velhos
- Gire o segredo no painel se vazar; atualize o receptor no mesmo dia
- Mantenha segredos completos e PII completa fora de logs compartilhados
Receita: painel comercial externo
- Ative webhooks e informe a URL HTTPS
- Assine apenas lead.captured e crm.card_moved
- Salve → Enviar evento de teste → confirme 2xx
- Em lead.captured: faça upsert do contato por e-mail, telefone ou lead.id
- Em crm.card_moved: identifique o contato por lead.phone, lead.email ou lead.id e atualize a fase com card.stageName (rótulo do kanban) ou card.stageId
- Ignore os demais tipos até precisar deles