Como receber mensagens do WhatsApp via webhook (exemplo em Node.js)
Se você quer que seu sistema reaja a cada mensagem que chega no WhatsApp, sem ficar consultando a API em loop, o caminho é o webhook. Em vez de você perguntar "chegou algo novo?", a plataforma avisa você: cada mensagem recebida vira uma requisição HTTP POST enviada para uma URL que você controla. É assim que se constroem chatbots, CRMs, sistemas de atendimento e automações que respondem em segundos. Neste artigo você vai entender o fluxo completo, ver um exemplo real em Node.js com Express e sair com um checklist de produção para não perder mensagem nem tomar timeout. O código serve para qualquer provedor de WhatsApp que envie eventos por webhook, e ao final mostro como isso funciona na prática com a ZapZap API.
O que é um webhook e por que ele é melhor que polling
Um webhook é um endpoint HTTP no seu servidor que fica esperando ser chamado. Quando um evento acontece no lado do WhatsApp, uma mensagem recebida, uma confirmação de leitura, uma mudança de status da conexão, o provedor faz um POST para a sua URL com os dados do evento no corpo da requisição. Você processa esse corpo e responde 200. Simples assim. A diferença para polling é enorme: no polling você precisa perguntar de tempos em tempos se chegou algo novo, o que gasta recurso, tem atraso e escala mal quando você tem muitos números conectados.
Com webhook a entrega é praticamente instantânea e você só é acionado quando realmente há algo para tratar. Isso reduz custo de infraestrutura e melhora a experiência: um cliente manda mensagem e seu bot pode responder em um ou dois segundos. Para atendimento, qualificação de lead e automação de vendas, essa latência baixa faz diferença direta na conversão.
O preço dessa arquitetura é que você precisa manter um endpoint público, estável e rápido. Se ele cair ou demorar demais, o provedor pode considerar a entrega falha e tentar reenviar depois, ou em alguns casos parar de tentar. Por isso, tratar bem a resposta e a idempotência não é detalhe, é o que separa uma integração de brinquedo de uma que aguenta produção.
Como é o payload de uma mensagem recebida
O formato exato varia entre provedores, mas a estrutura conceitual é sempre parecida. O corpo do POST costuma trazer um campo indicando o tipo de evento (por exemplo messages ou message), o identificador da instância ou número que recebeu, os dados de quem enviou (número, nome no WhatsApp), o conteúdo da mensagem e um campo booleano dizendo se a mensagem foi enviada por você mesmo (o famoso fromMe).
Esse fromMe é um dos maiores causadores de bug em integração de WhatsApp. Quando seu próprio sistema envia uma mensagem, muitos provedores também disparam um evento de webhook para ela. Se você não filtrar por fromMe, seu bot pode responder à própria mensagem e entrar em loop. A regra prática: no início do handler, se o evento for de mensagem enviada por você, retorne 200 e não faça nada.
Vale também tratar os tipos de conteúdo. Texto puro é o caso mais simples, mas você vai receber áudio, imagem, documento, localização, resposta de botão e reação. Cada um chega com uma estrutura de campos diferente. Comece tratando texto, garanta o fluxo, e só depois adicione os demais tipos conforme a necessidade real do seu produto. Não tente cobrir tudo de uma vez.
Antes de escrever qualquer lógica pesada, faça um log cru do corpo recebido em ambiente de teste e mande uma mensagem de verdade para o número. Ver o JSON real chegando é a forma mais rápida e honesta de entender o formato do seu provedor. Documentação ajuda, mas o payload real manda.
Exemplo prático: um webhook em Node.js com Express
Abaixo está um handler mínimo, mas já com os cuidados que importam. Ele usa Express, faz o parse de JSON, ignora mensagens próprias, extrai o texto e responde 200 rápido. O processamento pesado (chamar banco, IA, enviar resposta) fica fora do caminho crítico da resposta HTTP.
```js const express = require('express'); const app = express(); app.use(express.json({ limit: '2mb' })); app.post('/webhook', (req, res) => { // 1. Responda rápido: confirme o recebimento primeiro res.sendStatus(200); const evento = req.body; // 2. Ignore mensagens enviadas por voce mesmo (evita loop) const msg = evento?.message || evento?.data || {}; if (msg.fromMe) return; // 3. Extraia os campos que voce precisa const de = msg.sender || msg.from; const texto = msg.text || msg.body || ''; if (!texto) return; // por ora, so trata texto // 4. Processe fora do caminho da resposta HTTP processarMensagem(de, texto).catch((e) => console.error('erro ao processar', e) ); }); async function processarMensagem(de, texto) { console.log(`Mensagem de ${de}: ${texto}`); // aqui voce chama seu banco, sua IA, ou envia a resposta } app.listen(3000, () => console.log('webhook ouvindo na porta 3000')); ```
Repare na ordem: o res.sendStatus(200) vem antes de processar. Isso é intencional. O provedor só precisa saber que você recebeu; ele não precisa esperar você terminar de pensar a resposta. Se você deixar o provedor esperando a IA gerar texto ou o banco responder, corre o risco de estourar o timeout dele e receber a mesma mensagem de novo em um retry. Confirme primeiro, trabalhe depois.
Os nomes de campo no exemplo (message, data, sender, text) são ilustrativos e podem mudar conforme o provedor. Ajuste depois de ver o payload real. Se você usa a ZapZap API, os formatos exatos de cada tipo de evento estão descritos de forma legível por máquina em api.zapzapapi.com/llms.txt, que serve como fonte única para montar seu parser sem adivinhação.
Boas práticas para não perder mensagem em produção
Responda sempre com 2xx e faça isso rápido. Um webhook que demora mais que poucos segundos é um webhook problemático. Mova qualquer trabalho demorado, chamada de IA, escrita em banco lento, envio de resposta, para fora da requisição, usando uma fila ou pelo menos um processamento assíncrono que não bloqueia a resposta. O endpoint tem uma única obrigação síncrona: dizer recebi.
Trate reentregas como algo esperado, não como exceção. Redes falham, timeouts acontecem, e um provedor sério vai reenviar o evento se não recebeu seu 200. Isso significa que a mesma mensagem pode chegar duas vezes. Se cada chegada dispara uma resposta ou uma escrita, você gera duplicidade. A defesa é idempotência: guarde o ID único da mensagem e, antes de processar, verifique se já tratou aquele ID. Se já tratou, responda 200 e pare.
Valide a origem do webhook. Sua URL é pública, então qualquer um que a descubra pode enviar POSTs falsos. Dependendo do provedor você valida um token no header, uma assinatura HMAC ou um segredo na própria URL. Nunca confie cegamente no corpo recebido. E registre logs das entregas com falha: se um POST seu retorna erro, você quer saber qual mensagem foi e por quê, para reprocessar sem perder o lead.
Um detalhe fácil de esquecer: em ambiente de desenvolvimento sua máquina local não tem URL pública. Use uma ferramenta de túnel para expor a porta local durante os testes, ou configure o webhook apontando para um servidor de staging real. Criar a instância apontando webhook para localhost simplesmente não recebe nada.
Recebendo mensagens do WhatsApp com a ZapZap API
A ZapZap API é uma API de WhatsApp não oficial baseada em instâncias: você conecta um número real lendo o QR code e passa a enviar e receber mensagens por API, sem depender da aprovação e das filas da API oficial. Cada número ativo custa R$15 por mês no modelo pré-pago, e há crédito grátis para você testar o fluxo completo antes de comprometer volume. Para quem precisa subir uma automação rápida sem burocracia de verificação, é o caminho mais direto.
Na prática, você cadastra a URL do seu webhook e cada instância passa a encaminhar os eventos recebidos para o seu endpoint no mesmo formato de POST que mostramos acima. Você conecta o handler Node.js, filtra fromMe, trata idempotência e já tem um bot funcional. Os detalhes de configuração e os formatos de payload estão documentados em /integracoes/webhook, e a referência geral dos recursos disponíveis fica em /api-whatsapp.
Além do básico de enviar e receber, a plataforma agrega recursos que importam quando você opera com número não oficial de verdade: aquecimento das instâncias em comunidade para dar histórico ao número, mecanismos de anti-bloqueio que reduzem o risco de restrição, IA configurável por instância e módulos para fluxos específicos. Você recebe a mensagem no seu webhook e decide se responde com sua própria lógica, com a IA da instância ou encaminha para seu CRM.
Um ponto de honestidade: nenhuma solução baseada em número não oficial elimina totalmente o risco de restrição da conta. O que uma boa camada de aquecimento e anti-bloqueio faz é reduzir esse risco e dar mais previsibilidade à operação. Trate o webhook como a porta de entrada e construa em cima dele com essa consciência, distribuindo volume e respeitando o comportamento de um usuário real.
Resumo rápido
- Webhook entrega mensagens em tempo real via POST no seu servidor, sem polling e com latência baixa.
- Sempre responda 200 rápido e processe o trabalho pesado de forma assíncrona, fora do caminho da resposta HTTP.
- Filtre fromMe logo no início do handler para evitar que o bot responda à própria mensagem e entre em loop.
- Trate reentregas com idempotência (guarde o ID da mensagem) e valide a origem do webhook por token ou assinatura.
- Na ZapZap API, cada instância encaminha eventos para sua URL; formatos em /integracoes/webhook e api.zapzapapi.com/llms.txt.
Leia também
Perguntas frequentes
Preciso de servidor com IP fixo para receber webhook?
Não precisa de IP fixo, mas precisa de uma URL pública e estável com HTTPS. Em produção use um domínio próprio apontando para seu servidor ou serviço de nuvem. Em desenvolvimento, use uma ferramenta de túnel para expor sua porta local temporariamente, já que localhost não recebe chamadas externas.
Por que meu bot fica respondendo à própria mensagem?
Porque você não está filtrando o campo fromMe. Muitos provedores disparam um evento de webhook também para as mensagens que o seu sistema envia. Verifique fromMe no início do handler e, se for verdadeiro, retorne 200 sem processar. Isso corta o loop na origem.
O que acontece se meu servidor cair e eu não responder 200?
O provedor considera a entrega falha e normalmente tenta reenviar o evento depois, dentro de uma janela de retry. Por isso trate reentregas com idempotência: guarde o ID da mensagem e ignore duplicatas. Se as falhas persistirem por muito tempo, você pode perder eventos, então monitore e registre entregas com erro.
Consigo receber mensagens sem a API oficial do WhatsApp?
Sim. A ZapZap API usa instâncias com número real conectado por QR code, o que permite enviar e receber por webhook sem a aprovação e as filas da API oficial. O custo é R$15 por número ativo ao mês, pré-pago, com crédito grátis para testar. Lembre que número não oficial sempre carrega algum risco de restrição, mitigado por aquecimento e anti-bloqueio.
Comece com crédito grátis
Crie sua conta, conecte um número em minutos e teste a API com aquecimento e anti-bloqueio. R$15 por número ativo/mês, pré-pago, sem fidelidade.
