← Blog

·

Como testar o webhook do WhatsApp localmente com ngrok

Atualizado em 25/10/2026

Para testar o webhook do WhatsApp localmente, rode seu servidor na máquina, exponha a porta com um túnel HTTPS (ngrok ou cloudflared) e cadastre a URL pública como Callback URL no painel do app da Meta. O servidor precisa responder ao GET de verificação devolvendo o hub.challenge e, nos POSTs, validar o cabeçalho X-Hub-Signature-256 com o segredo do app. Com isso, você vê os eventos reais chegando no seu terminal, sem deploy.

Este guia traz um servidor Node.js completo, os comandos dos dois túneis, o botão de teste do painel da Meta e os erros mais comuns nessa etapa.

Por que precisa de túnel

A Meta só entrega webhooks para uma URL pública com HTTPS. A documentação de webhooks exige um certificado TLS válido e diz que certificados autoassinados não são aceitos. http://localhost:3000 não atende a nenhum dos dois requisitos.

Um túnel resolve isso: o agente roda na sua máquina, abre uma conexão de saída com o serviço e recebe uma URL pública com HTTPS que encaminha as requisições para a sua porta local. Não é preciso abrir porta no roteador nem ter IP fixo.

FerramentaComandoURL geradaObservações
ngrokngrok http 3000HTTPS, mostrada no console do agentePede conta e authtoken; tem inspetor de tráfego e reenvio de requisições
cloudflared (Quick Tunnel)cloudflared tunnel --url http://localhost:3000Subdomínio temporário em trycloudflare.comNão pede conta; o endereço muda a cada execução; uso só para teste

Como a Meta conversa com o seu endpoint

São duas requisições diferentes na mesma URL:

  1. GET de verificação. Quando você salva ou edita a Callback URL ou o token de verificação no painel, a Meta manda um GET com hub.mode=subscribe, hub.challenge (texto aleatório) e hub.verify_token (o valor que você digitou no painel). Se o token confere, responda 200 com o valor de hub.challenge no corpo. Se não confere, responda 403 ou outro código diferente de 200.
  2. POST de eventos. Cada mensagem recebida ou mudança de status chega como JSON, com o cabeçalho X-Hub-Signature-256: sha256=<hash>. O hash é um HMAC-SHA256 do corpo, com o segredo do app como chave. Responda 200 para confirmar.

Se o POST falhar, a Meta tenta de novo na hora e depois várias vezes, com frequência decrescente, por até 7 dias. Entregas não confirmadas nesse prazo são descartadas. Por isso, o mesmo evento pode chegar mais de uma vez, e o servidor precisa deduplicar. A Meta também avisa que não há API para buscar webhooks antigos: guarde o que receber.

Servidor Node.js de teste

O exemplo usa Express. O ponto mais importante é guardar o corpo bruto da requisição para calcular a assinatura. Se você calcular o HMAC sobre um JSON reserializado, os bytes podem mudar e a comparação falha.

mkdir webhook-local && cd webhook-local
npm init -y
npm install express
// server.js
import express from 'express';
import crypto from 'node:crypto';

const { VERIFY_TOKEN, APP_SECRET, PORT = 3000 } = process.env;
const app = express();

// Guarda o corpo bruto para validar a assinatura
app.use(express.json({
  verify: (req, _res, buf) => { req.rawBody = buf; }
}));

// 1) Verificação (GET)
app.get('/webhook', (req, res) => {
  const mode = req.query['hub.mode'];
  const token = req.query['hub.verify_token'];
  const challenge = req.query['hub.challenge'];

  if (mode === 'subscribe' && token === VERIFY_TOKEN) {
    console.log('Webhook verificado');
    return res.status(200).send(challenge);
  }
  return res.sendStatus(403);
});

// 2) Eventos (POST)
app.post('/webhook', (req, res) => {
  const header = req.get('x-hub-signature-256') || '';
  const expected = 'sha256=' + crypto
    .createHmac('sha256', APP_SECRET)
    .update(req.rawBody || '')
    .digest('hex');

  const valid = header.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));

  if (!valid) {
    console.warn('Assinatura inválida');
    return res.sendStatus(401);
  }

  res.sendStatus(200); // confirma rápido

  for (const entry of req.body.entry || []) {
    for (const change of entry.changes || []) {
      const v = change.value || {};
      for (const m of v.messages || []) console.log('mensagem', m.from, m.type, m.id);
      for (const s of v.statuses || []) console.log('status', s.id, s.status);
    }
  }
});

app.listen(PORT, () => console.log(`Ouvindo em http://localhost:${PORT}`));

Para rodar, adicione "type": "module" no package.json e inicie com as variáveis:

VERIFY_TOKEN=um-texto-que-voce-escolhe APP_SECRET=segredo_do_app node server.js

O VERIFY_TOKEN é qualquer texto que você define e repete no painel. O APP_SECRET é a chave secreta do app, em Configurações do app → Básico no painel de desenvolvedores. Nunca coloque essa chave no repositório.

Passo a passo com ngrok

  1. Crie uma conta no ngrok e instale o agente (no macOS, brew install ngrok).
  2. Conecte o agente à conta:
ngrok config add-authtoken SEU_AUTHTOKEN
  1. Com o servidor rodando na porta 3000, abra o túnel:
ngrok http 3000
  1. Copie a URL HTTPS de encaminhamento que aparece no console.
  2. No painel do app da Meta, vá em WhatsApp → Webhooks → Configuração (o nome do menu pode variar), cole https://SUA-URL/webhook em Callback URL, digite o mesmo VERIFY_TOKEN em Token de verificação e clique em Verificar e salvar. O terminal deve mostrar “Webhook verificado”.
  3. Na lista de campos, assine o campo messages.
  4. Clique em Testar ao lado de messages. A Meta envia um payload de exemplo, que deve aparecer no seu terminal. Segundo o guia de webhook de teste da Meta, o teste do campo messages funciona com o app em modo de desenvolvimento ou publicado.
  5. Mande uma mensagem real do seu celular para o número de teste ou de produção conectado ao app e veja o evento chegar.

O agente do ngrok tem um inspetor de tráfego, normalmente em http://localhost:4040, que mostra cabeçalhos, corpo e resposta de cada requisição. Com o modo de captura completa ativado na conta, o Traffic Inspector permite reenviar uma requisição para o seu servidor sem esperar a Meta, o que acelera muito a depuração do parser.

Passo a passo com cloudflared

Se não quiser criar conta, o Quick Tunnel da Cloudflare resolve para testes rápidos:

cloudflared tunnel --url http://localhost:3000

O comando imprime uma URL em trycloudflare.com. Use-a no painel da Meta do mesmo jeito. Pela documentação da Cloudflare, o Quick Tunnel é para teste e desenvolvimento, não tem garantia de disponibilidade, aceita até 200 requisições simultâneas e gera um endereço novo a cada execução. Como a URL muda, você precisa refazer a verificação no painel toda vez que reiniciar o túnel.

Erros comuns e como resolver

SintomaCausa provávelComo resolver
“Não foi possível validar a URL de callback” no painelToken diferente, servidor fora do ar, rota errada ou resposta com JSON em vez do texto do challengeResponda só o valor de hub.challenge, com status 200, na rota exata cadastrada
GET de verificação funciona, mas nenhum POST chegaCampo messages não assinado, ou a conta do WhatsApp não está ligada ao appAssine o campo e confira o app vinculado à conta
Toda assinatura dá inválidaHMAC calculado sobre JSON reserializado, segredo errado ou cabeçalho sem o prefixo sha256=Use o corpo bruto e o segredo do app (não o token de acesso)
O mesmo evento chega várias vezesResposta lenta ou diferente de 200, gerando novas tentativasResponda 200 antes de processar e deduplique pelo id
Parou de funcionar depois de reiniciar o túnelA URL pública mudouAtualize a Callback URL e verifique de novo

Um cuidado extra: o webhook é configurado por app. Se você apontar a Callback URL do app de produção para a sua máquina, a produção para de receber eventos. Use um app de desenvolvimento separado para testes locais.

Do teste para a produção

O túnel é ferramenta de desenvolvimento. Em produção, o endpoint precisa de domínio próprio, certificado válido, fila para processar os eventos fora da requisição e armazenamento dos payloads. O artigo webhooks da API do WhatsApp cobre a arquitetura, e o guia de status de mensagem mostra como tratar sent, delivered, read e failed. Quando o webhook trouxer erros, a tabela de erros da API do WhatsApp ajuda a decidir o que fazer. Se o app estiver usando um token temporário, troque por um token permanente antes de publicar.

Como isso funciona na Vectalk

Na Vectalk, os eventos são os mesmos da Meta, no mesmo formato entry[].changes[].value, então o parser do exemplo acima serve sem mudanças. O que muda é o cadastro: em vez do painel da Meta, você registra a URL por API, com instance_id, url (HTTPS) e name, e pode ter várias URLs por instância, todas recebendo o mesmo evento.

curl -X POST https://apis.vectalk.com.br/api/webhooks \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "instance_id": "SUA_INSTANCIA",
    "url": "https://SUA-URL-DO-TUNEL/webhook",
    "name": "Dev local"
  }'

Isso permite registrar a URL do túnel como um webhook extra, sem tirar o de produção, e desativá-lo depois com active: false no endpoint de atualizar webhook. A Vectalk espera 200 em menos de 20 segundos e reenvia o evento se não receber, segundo a documentação de webhooks. Antes de exigir validação de assinatura nesse fluxo, confira na documentação e no inspetor do túnel quais cabeçalhos chegam.

Crie sua conta em apps.vectalk.com.br/register. Para a visão geral da plataforma, veja o guia da API oficial do WhatsApp.

Perguntas frequentes

Posso cadastrar http://localhost como webhook do WhatsApp?

Não. A Meta exige uma URL pública com HTTPS e certificado válido. Para testar na sua máquina, use um túnel como ngrok ou cloudflared.

O que devo responder no GET de verificação?

Status 200 com o valor de hub.challenge no corpo, sem JSON e sem aspas, desde que hub.verify_token seja igual ao token que você cadastrou.

Qual segredo uso para validar o X-Hub-Signature-256?

A chave secreta do app (App Secret), não o token de acesso nem o token de verificação. O hash é HMAC-SHA256 do corpo da requisição, comparado ao valor depois de sha256=.

Por que recebo o mesmo webhook duas vezes?

Porque a Meta tenta reenviar quando não recebe 200, por até 7 dias. Responda rápido e deduplique pelo id da mensagem ou do status.

Dá para usar o ngrok em produção?

Ele é pensado para expor serviços locais em desenvolvimento. Para produção, hospede o endpoint em um servidor com domínio e certificado próprios.


Fontes oficiais consultadas em 25/10/2026: Criar endpoint de webhook (WhatsApp), Webhook de teste com echo bot, Webhooks da Graph API: primeiros passos, ngrok: Share Localhost Quickstart, ngrok: Test Webhooks Locally, ngrok: Traffic Inspector, Cloudflare Quick Tunnels, Webhooks na documentação da Vectalk, Atualizar webhook na documentação da Vectalk.

Integre o WhatsApp oficial ao seu sistema

A Vectalk entrega a Cloud API da Meta com os mesmos payloads e webhooks, um token para todas as contas e documentação completa.