Tutorial dev

Tutorial: webhook WhatsApp em Node.js (código completo)

Vou mostrar do zero como receber mensagem WhatsApp via Cloud API da Meta usando Node.js + Express, validar a assinatura HMAC pra segurança e processar diferentes tipos de mensagem (texto, áudio, imagem, status). Código testado em produção, pronto pra copiar.

15 de maio de 2026 · 12 min de leitura · MercaBot

Pré-requisitos

Passo 1 — Setup do projeto

mkdir wa-webhook && cd wa-webhook
npm init -y
npm install express
# crypto já vem no Node core, não precisa instalar
touch server.js .env

Arquivo .env:

WHATSAPP_TOKEN=EAAxxxx...
APP_SECRET=abc123...
PHONE_NUMBER_ID=109876543210987
VERIFY_TOKEN=meu_token_secreto_aleatorio_xyz
PORT=3000

Passo 2 — Endpoint de verificação (GET)

Quando você cadastra o webhook no Business Manager, a Meta envia um GET com hub.challenge. Seu endpoint precisa retornar exatamente esse valor de volta, autenticando que você "é o dono" do endpoint.

// server.js
import express from 'express';
import crypto from 'crypto';

const app = express();

// IMPORTANTE: precisa do raw body pra validar assinatura depois
app.use(express.json({
  verify: (req, _res, buf) => { req.rawBody = buf; }
}));

const { WHATSAPP_TOKEN, APP_SECRET, PHONE_NUMBER_ID, VERIFY_TOKEN, PORT = 3000 } = process.env;

// Verificação inicial do webhook
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);
});

Passo 3 — Endpoint de recebimento (POST)

Aqui chegam os eventos: mensagem nova, status de envio, mudança de perfil, etc. Sempre responda 200 rapidamente (em <5 segundos), senão a Meta retenta e pode banir seu webhook.

// Recepção de eventos
app.post('/webhook', (req, res) => {
  // 1) Validar assinatura antes de qualquer coisa
  if (!verificarAssinatura(req)) {
    console.warn('Assinatura inválida — rejeitando');
    return res.sendStatus(401);
  }

  // 2) Responder 200 já (processar async)
  res.sendStatus(200);

  // 3) Processar evento
  processarEvento(req.body).catch(err => console.error('Erro processando:', err));
});

function verificarAssinatura(req) {
  const sig = req.get('x-hub-signature-256');
  if (!sig) return false;
  const esperado = 'sha256=' + crypto
    .createHmac('sha256', APP_SECRET)
    .update(req.rawBody)
    .digest('hex');
  // crypto.timingSafeEqual evita timing attack
  try {
    return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(esperado));
  } catch { return false; }
}

Passo 4 — Processar a mensagem

O payload da Meta tem estrutura aninhada. Aqui está o parser pros 4 tipos mais comuns:

async function processarEvento(body) {
  // Payload: { object, entry: [{ changes: [{ value: { ... } }] }] }
  const entry = body.entry?.[0];
  const change = entry?.changes?.[0]?.value;
  if (!change) return;

  // a) Mensagem recebida
  const msgs = change.messages || [];
  for (const msg of msgs) {
    const from = msg.from; // número do remetente
    const id = msg.id;

    if (msg.type === 'text') {
      const texto = msg.text.body;
      console.log(`📩 ${from}: ${texto}`);
      await responder(from, `Recebi: "${texto}"`);
    }
    else if (msg.type === 'image' || msg.type === 'audio' || msg.type === 'video' || msg.type === 'document') {
      const mediaId = msg[msg.type].id;
      console.log(`📷 ${from} mandou ${msg.type} id=${mediaId}`);
      // Pra baixar o arquivo: GET https://graph.facebook.com/v21.0/{mediaId}
      // (com Bearer token) → retorna URL com TTL curto pra download
    }
    else if (msg.type === 'button' || msg.type === 'interactive') {
      // Botão de template ou lista interativa clicado
      console.log(`🔘 ${from} clicou em botão`);
    }
  }

  // b) Status de envio (delivered, read, failed)
  const statuses = change.statuses || [];
  for (const st of statuses) {
    console.log(`✓ status msg=${st.id} status=${st.status}`);
  }
}

Passo 5 — Enviar mensagem de volta

async function responder(to, texto) {
  const url = `https://graph.facebook.com/v21.0/${PHONE_NUMBER_ID}/messages`;
  const body = {
    messaging_product: 'whatsapp',
    recipient_type: 'individual',
    to,
    type: 'text',
    text: { body: texto, preview_url: false }
  };
  const r = await fetch(url, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${WHATSAPP_TOKEN}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(body)
  });
  if (!r.ok) {
    console.error('Erro enviando:', r.status, await r.text());
  }
}

app.listen(PORT, () => console.log(`🟢 Webhook ouvindo na porta ${PORT}`));

Passo 6 — Deploy

Render é o mais simples pra testar (free tier suficiente). Alternativas: Railway, Fly.io, AWS Lambda.

  1. Crie repo no GitHub com o código.
  2. Em render.com: New → Web Service → conecte ao repo.
  3. Build command: npm install. Start command: node server.js.
  4. Adicione as variáveis de ambiente (WHATSAPP_TOKEN, APP_SECRET, etc.).
  5. Deploy. Pegue a URL pública (ex: https://wa-webhook.onrender.com).

Passo 7 — Cadastrar webhook no Business Manager

  1. developers.facebook.com → seu app → WhatsApp → Configuration.
  2. Em "Webhook", clique Configure.
  3. Callback URL: https://wa-webhook.onrender.com/webhook.
  4. Verify Token: o mesmo valor de VERIFY_TOKEN do seu .env.
  5. Clique Verify and Save. Meta faz GET → seu endpoint responde challenge → cadastrado.
  6. Em "Webhook fields", clique Manage e ative pelo menos messages e message_template_status_update.

Testando

  1. Mande mensagem do seu celular pessoal pro número WhatsApp da empresa.
  2. Logs do servidor devem mostrar: 📩 5511...: oi teste.
  3. Você deve receber de volta: Recebi: "oi teste".

Erros comuns

Quando vale a pena fazer do zero (e quando não)

Construir do zero faz sentido se você é dev construindo SaaS com WhatsApp embutido no produto (ex: app de delivery, fintech mandando notificação). Precisa de controle total do código, integração com microsserviços internos, deploy próprio.

Não faz sentido se você só quer atender cliente no WhatsApp da empresa — gastar 2-4 semanas implementando painel multi-atendente, bot IA, broadcast HSM e templates do zero é trabalho perdido. Plataforma como a MercaBot resolve em 3 minutos por R$ 197/mês.

Atendimento WhatsApp sem código

Se o objetivo é atender cliente (não construir produto), pule a parte de webhook. MercaBot conecta em 3 minutos com painel pronto + bot IA + broadcast.

Testar grátis →