Webhook: receber as mensagens no seu servidor

Seu sistema recebe cada mensagem que chega ao WhatsApp, assinada, e, se quiser, os avisos de conexão. Veja o que chega, como validar e os limites.

Com o webhook, o seu servidor recebe um aviso (um POST em JSON) a cada mensagem que chega ao seu WhatsApp, sem ficar consultando. A mensagem sai do seu ambiente Coloquia direto para o endereço que você escolher: o conteúdo não passa pelo nosso painel, que guarda só o endereço e o segredo.

Como ligar

  1. No portal, abra Conectar a IA, clique em Configurar o webhook e informe o endereço do seu servidor: https, público e na porta 443.
  2. Copie o segredo (aparece uma única vez; se perder, gere outro) e use Enviar teste para receber um evento de exemplo (event = webhook.test).

Pela API, as mesmas ações ficam em /api/v1/webhook (GET, PUT, DELETE), /api/v1/webhook/secret e /api/v1/webhook/test. Se a sua conta tem passkey, ligar, trocar o endereço e gerar segredo só funcionam no portal, com a confirmação da passkey.

O que chega

Cada entrega traz três cabeçalhos: X-Coloquia-Id (o identificador do evento, igual ao event_id do corpo), X-Coloquia-Timestamp (segundos Unix) e X-Coloquia-Signature.

{"event_id": "00112233445566778899aabbccddeeff", "event": "message.received", "message_id": "ID-DE-EXEMPLO",
 "chat": "[email protected]", "sender": "[email protected]", "type": "text",
 "text": "Bom dia!", "timestamp": "2026-10-13T12:00:00Z", "from_me": false, "stored": true}

Por padrão chegam só as mensagens que outras pessoas mandam para você nas conversas. O resto você liga em O que enviar (veja abaixo).

Se o seu sistema responde sozinho, ignore os eventos com from_me igual a true, a menos que você tenha ligado Mensagens enviadas pelo seu número. Em ambientes que ainda não foram atualizados (a atualização é feita por nós), as mensagens do próprio número podem chegar mesmo com a opção desligada, e um sistema que reage a elas responde às próprias respostas, sem parar.

Mídia não é enviada. Foto, áudio, vídeo e documento chegam com o type, a legenda em text e uma referência em media_reference; o arquivo você busca pela sua IA conectada.

O que enviar

Na tela do webhook, o bloco O que enviar tem quatro opções, todas desligadas no começo. Ligar qualquer uma amplia o que sai do seu ambiente, então o portal pede confirmação (e a passkey, se a sua conta tiver). Desligar nunca é recusado: a escolha fica guardada na hora e chega ao seu ambiente em alguns segundos (a tela mostra "Aplicando" enquanto isso). Também dá para desligar pela API (PUT /api/v1/webhook com o mesmo endereço e o campo em false), mesmo com passkey na conta; ligar, só no portal.

  • Mudanças de conexão (connection_events): eventos connection.changed, explicados abaixo.
  • Mensagens enviadas pelo seu número (forward_self): chegam também como message.received, com from_me igual a true, tanto as que você escreve no celular quanto as que a sua IA envia. Vem desligado porque um sistema que responde sozinho reagiria às próprias respostas: se ligar, ignore os eventos com from_me igual a true antes de responder.
  • Canais (forward_channels): as publicações dos canais que o seu número segue, com chat terminando em @newsletter.
  • Listas de transmissão (forward_broadcasts): as mensagens das listas de transmissão, com chat terminando em @broadcast.

Os status dos seus contatos nunca são enviados: são publicações para todos os contatos, não conversas. Em um ambiente que ainda não recebeu a atualização (feita por nós), a tela avisa que as opções chegam com ela: a sua escolha fica guardada e, até lá, só as mensagens recebidas são enviadas. Se a configuração do seu ambiente proibir uma opção, ela aparece como proibida pela configuração do ambiente: não é enviada, e as outras continuam valendo.

Ao trocar o endereço ou religar o webhook, as opções voltam a desligadas: ligue de novo as que quiser para o destino novo.

O evento de conexão

Com Mudanças de conexão ligado, o seu servidor recebe um aviso quando o número muda de situação. Ele não traz número, conversa nem mensagem:

{"event_id": "ffeeddccbbaa99887766554433221100", "event": "connection.changed", "type": "connection",
 "state": "disconnected", "reason": "transport_lost", "at": "2026-10-13T12:00:00Z"}

O campo state diz o que aconteceu:

  • connected: o número está conectado e recebendo mensagens.
  • disconnected: a conexão caiu. Quedas que voltam em menos de 5 segundos não geram aviso. Se o WhatsApp restringiu o número, o evento traz também connection_problem, com o tipo da restrição em kind.
  • logged_out: o número saiu da conta (pelo celular ou pelo portal). A entrega de mensagens para até um novo pareamento.
  • pairing_required: o ambiente espera um novo pareamento. Abra o portal para conectar de novo.
  • paired: o pareamento acabou de terminar.

at é o horário da mudança, e reason é um rótulo técnico, útil para o suporte. Ao ligar a opção, o seu servidor recebe na hora o estado atual, com o at de quando ele começou: um sistema configurado agora já sabe, por exemplo, que o número espera pareamento.

Como validar a assinatura

A assinatura é sha256= seguido do HMAC-SHA256, com o seu segredo, de <timestamp>.<corpo>. Use o corpo exatamente como chegou, sem reescrever o JSON.

import hashlib, hmac, json, time

def verify_webhook(headers, raw_body, secret, prefix="X-Coloquia"):
    timestamp = headers.get(prefix + "-Timestamp", "")
    try:
        signature = headers.get(prefix + "-Signature", "").encode("ascii")
        fresh = abs(int(time.time()) - int(timestamp)) <= 300
        signed = timestamp.encode("ascii") + b"." + raw_body
    except (ValueError, TypeError, UnicodeError):
        return False
    expected = "sha256=" + hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()
    if not fresh or not hmac.compare_digest(expected.encode("ascii"), signature):
        return False
    try:
        event = json.loads(raw_body)
    except (ValueError, UnicodeError):
        return False
    event_id = event.get("event_id") if isinstance(event, dict) else None
    return isinstance(event_id, str) and bool(event_id) and event_id == headers.get(prefix + "-Id")

Responda com um status 2xx em até 10 segundos. A mesma entrega pode chegar mais de uma vez, de mensagem ou de conexão: guarde o event_id, ignore o que já processou e responda 2xx também à repetição.

Limites

  • Cada evento tem até cinco tentativas, por até cinco minutos. Respostas 4xx (menos 408 e 429) não são repetidas. A fila fica na memória do seu ambiente: 256 eventos. O que passar disso, ou estiver na fila quando o ambiente reiniciar, é perdido. Depois de um reinício, a entrega volta sozinha em alguns minutos; o que chegar nesse intervalo não é enviado.
  • Depois de 50 eventos seguidos sem sucesso, o webhook é desligado e você recebe um e-mail. O mesmo acontece se o endereço deixar de ser aceito (domínio vencido, ou que passou a apontar para uma rede interna). Para religar, salve o endereço de novo. As mensagens continuam todas disponíveis para a sua IA, com ou sem webhook.