Skip to Content
Webhooks de saída

Webhooks de saída (integrar seu sistema)

Um webhook de saída é um endereço do seu sistema que a plataforma chama toda vez que algo acontece na conversa. É assim que você grava o atendimento inteiro no seu próprio banco de dados — por exemplo, num SaaS de Ordem de Serviço.

Cadastrar o destino

Abra a seção Webhooks

No agent, clique em Webhooks no menu lateral (“Envie as conversas para seu sistema”).

Escolha o que a conexão faz

  • Espelho de eventos — o que esta página descreve: uma cópia de cada mensagem vai para o seu sistema, que não precisa responder nada.
  • Ação na conversa — o agent chama o seu sistema durante o atendimento e usa a resposta. Está em Agents trabalhando juntos.

O tipo não muda depois de criado.

Preencha o destino

  • Nome — como você quer chamar esse destino (ex.: SaaS de OS).
  • URL — o endereço HTTPS do seu sistema que vai receber os avisos.
  • Secret — uma senha com pelo menos 16 caracteres. É com ela que você confirma que o aviso veio mesmo da plataforma.

Escolha os eventos

Os dois eventos vêm marcados por padrão:

  • Mensagens do cliente (conversation.inbound)
  • Respostas do agent (hermes.output)

Salve

O destino aparece na lista com a etiqueta Enviando. Você pode editar, pausar e remover depois.

O secret é gravado e nunca mais exibido. Ao editar o destino, o campo aparece como •••••••• (manter atual): deixar assim mantém o secret atual, e preencher troca por um novo. Guarde a sua cópia no cofre de senhas do seu sistema.

Os eventos

EventoQuando aconteceO que vem
conversation.inboundQuando o lote de mensagens do cliente é levado para o agent responderprovider e a lista de mensagens do cliente, cada uma com type, content e received_at
hermes.outputQuando o agent produz uma respostaO conteúdo da resposta produzida pelo agent
conversation.operatorQuando um atendente humano digita no número — nos dois canais (no oficial/Datafy, depende da assinatura smb_message_echoes no painel)A fala do atendente, para o seu sistema registrar o atendimento humano
conversation.handoffQuando a conversa é entregue a uma pessoa — pelo próprio agent ou pela API de pausaQuem entregou, o canal, a etiqueta aplicada e o motivo

Os checkboxes da tela cobrem os dois primeiros. O conversation.operator e o conversation.handoff existem e são disparados, mas hoje só podem ser assinados pela API (incluindo o nome do evento na lista do destino, ou usando * para receber tudo).

Destinos criados antes de um evento novo existir não passam a receber esse evento sozinhos. A assinatura é sempre explícita: edite o destino e marque o evento.

O que chega no seu sistema

A plataforma faz um POST com JSON e três cabeçalhos:

POST /seu/endpoint HTTP/1.1 Content-Type: application/json X-Gateway-Event: conversation.inbound X-Gateway-Event-Id: 5b7c1f8d-8c3f-4a2c-8a1b-123456789abc X-Gateway-Signature: sha256=ASSINATURA_EM_HEXADECIMAL

O corpo sempre traz o mesmo envelope: event_id, event_type, source, org_id, agent_id, conversation_id, created_at e o payload do evento.

Exemplo de conversation.handoff (a conversa acabou de ser entregue a uma pessoa):

{ "event_id": "5b7c1f8d-8c3f-4a2c-8a1b-123456789abc", "event_type": "conversation.handoff", "source": "hermes", "org_id": "<ID_DA_ORG>", "agent_id": "<ID_DO_AGENT>", "conversation_id": "5511999999999@s.whatsapp.net", "created_at": "2026-09-10T13:45:10.000Z", "payload": { "by": "agent:<ID_DO_AGENT>", "provider": "uazapi", "label": "atendimento humano", "label_status": "applied", "reason": "cliente pediu para falar com uma pessoa" } }

by diz quem entregou: agent:<id> quando foi o próprio agent, ou a credencial que chamou a API de pausa. label_status vale applied, not_found (a etiqueta não existe na conta), not_supported (canal sem etiqueta, ou nenhuma pedida) ou failed. Numa pausa pela API, label e reason vêm null.

Validar a assinatura (obrigatório)

A assinatura é um HMAC SHA-256 do corpo bruto da requisição, usando o seu secret. Valide antes de interpretar ou salvar qualquer coisa — sem isso, qualquer um que descubra a sua URL consegue gravar dados falsos no seu sistema.

Exemplo genérico em Node.js:

import { createHmac, timingSafeEqual } from "node:crypto"; // SEU_SECRET vem do seu cofre de segredos — nunca escrito no código. const secret = process.env.GATEWAY_WEBHOOK_SECRET; export async function receberWebhook(request) { const corpoBruto = await request.text(); // o corpo CRU, antes de qualquer parse const recebida = request.headers.get("x-gateway-signature") ?? ""; const esperada = "sha256=" + createHmac("sha256", secret).update(corpoBruto).digest("hex"); const valida = recebida.length === esperada.length && timingSafeEqual(Buffer.from(recebida), Buffer.from(esperada)); if (!valida) return new Response("assinatura inválida", { status: 401 }); const evento = JSON.parse(corpoBruto); // ... aqui você grava no seu banco return new Response("ok", { status: 200 }); }

Três detalhes que costumam quebrar a validação:

  1. Use o corpo bruto. Se o seu framework já converteu para objeto e você reserializar, um único espaço diferente muda a assinatura.
  2. Compare em tempo constante (timingSafeEqual), não com ===.
  3. Responda 2xx só depois de aceitar o evento.

Caso de uso: gravar a conversa no seu SaaS

Assinando os dois eventos, o seu sistema recebe o diálogo completo — o que o cliente falou (conversation.inbound) e o que o agent respondeu (hermes.output) — e pode montar a timeline do atendimento na ficha da Ordem de Serviço.

Roteiro sugerido para o seu endpoint:

  1. Validar a assinatura.
  2. Ignorar o evento se o event_id já tiver sido processado (idempotência).
  3. Localizar/criar o atendimento pelo conversation_id.
  4. Gravar a mensagem com a data de created_at.
  5. Responder 200.

Guarde o event_id de tudo que você processar. A plataforma tenta entregar duas vezes, com 10 segundos de limite por tentativa — se a primeira demorar demais e a segunda chegar, sem idempotência você grava a mesma mensagem duas vezes.

Problemas comuns

SintomaCausa provávelO que fazer
“Preencha o nome, a URL HTTPS, um secret de 16 caracteres e ao menos um evento.”Algum campo faltando ou secret curto demaisComplete o formulário — a mensagem aparece no próprio card
O destino não recebe nadaEstá pausado, ou o evento não está marcadoConfira a etiqueta (Enviando × Pausado) e os eventos do destino
Recebo só metade da conversaSó um dos dois eventos está assinadoMarque Mensagens do cliente e Respostas do agent
Assinatura sempre inválidaVocê validou o corpo já convertido, e não o corpo brutoValide sobre o texto cru da requisição
Mensagens duplicadas no meu bancoSem controle de idempotênciaGuarde e confira o event_id
Perdi o secretEle nunca é exibido de novoEdite o destino e grave um secret novo dos dois lados
Última atualização em