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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

Cabeçalhos
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>
Envelope (todo evento)
{
  "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.

Esboço de verificação em Node.js
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.

data de lead.captured
{
  "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.

data de crm.card_moved
{
  "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

data de handoff.requested
{
  "handoff": {
    "id": "ho_…",
    "reason": "Visitante pediu uma pessoa",
    "conversationTitle": "Chat do widget"
  }
}

Ciclo de handoff (claimed / returned / closed / requeued)

data dos eventos de ciclo de handoff
{
  "handoff": {
    "id": "ho_…",
    "conversationId": "conv_…"
  },
  "actor": { "id": "user_…", "name": "Erika" }
}

crm.session_promoted

data de 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)

data de dispatch.*
{
  "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)

data de ping
{
  "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

  1. Ative webhooks e informe a URL HTTPS
  2. Assine apenas lead.captured e crm.card_moved
  3. Salve → Enviar evento de teste → confirme 2xx
  4. Em lead.captured: faça upsert do contato por e-mail, telefone ou lead.id
  5. 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
  6. Ignore os demais tipos até precisar deles