Sim. Para receber mensagens do WhatsApp em um sistema próprio, você precisa de um endpoint HTTPS público, cadastrar esse endereço no painel da Meta e assinar o campo messages da WhatsApp Cloud API. O servidor recebe um webhook — uma chamada HTTP automática — sempre que chega uma mensagem ou muda o status de uma mensagem enviada.
O exemplo abaixo usa Node.js e Express para validar o endpoint, conferir a assinatura da Meta e extrair o texto recebido. Ele serve como base para atendimento de clientes, triagem em uma clínica, pedidos de um restaurante ou integração com um CRM, sem depender de bibliotecas não oficiais que imitam o WhatsApp Web.

O que você precisa antes de começar
- uma conta de desenvolvedor da Meta e um aplicativo com o caso de uso do WhatsApp;
- uma conta WhatsApp Business e um número configurado para a Cloud API;
- o Phone Number ID, o token de acesso e o App Secret;
- um servidor acessível pela internet com HTTPS válido. Certificado autoassinado não é aceito pela validação dos Webhooks da Meta;
- Node.js e npm instalados no ambiente onde o endpoint vai rodar.
Para testar sem colocar um servidor caseiro na internet, use um ambiente de desenvolvimento que forneça uma URL HTTPS pública e encaminhe as requisições para sua porta local. Em produção, prefira um domínio próprio, logs protegidos e um processo que reinicie automaticamente.
Como funciona o webhook do WhatsApp
A configuração tem duas chamadas diferentes. Primeiro, a Meta faz um GET de verificação com hub.verify_token e hub.challenge. Seu servidor compara o token e devolve o challenge. Depois, os eventos chegam por POST em JSON.
Em uma mensagem recebida, o caminho mais importante costuma ser entry[0].changes[0].value.messages[0]. O objeto traz o remetente em from, o tipo em type e, quando é texto, o conteúdo em text.body. Status de mensagens enviadas usam o array statuses, não messages.
Há também uma camada de segurança que muita integração de tutorial esquece: a Meta envia o cabeçalho X-Hub-Signature-256. Ele é uma assinatura HMAC-SHA256 do corpo usando o App Secret. Conferir esse valor evita aceitar qualquer POST forjado como se fosse um evento legítimo.
1. Crie o projeto Node.js
mkdir whatsapp-webhook
cd whatsapp-webhook
npm init -y
npm install express
Crie um arquivo chamado server.js e cole o código abaixo. Ele aceita textos e ignora outros tipos por enquanto; depois você pode tratar imagens, áudios, documentos e mensagens interativas em blocos separados.
const express = require("express");
const crypto = require("node:crypto");
const app = express();
const PORT = process.env.PORT || 3000;
const VERIFY_TOKEN = process.env.WA_VERIFY_TOKEN;
const APP_SECRET = process.env.META_APP_SECRET;
function isValidSignature(rawBody, signatureHeader) {
if (!APP_SECRET || !signatureHeader?.startsWith("sha256=")) return false;
const received = Buffer.from(signatureHeader.slice(7), "hex");
const expected = crypto
.createHmac("sha256", APP_SECRET)
.update(rawBody)
.digest();
return received.length === expected.length &&
crypto.timingSafeEqual(received, expected);
}
// A Meta usa GET para validar o endpoint antes de enviar eventos.
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) {
return res.status(200).send(challenge);
}
return res.sendStatus(403);
});
// O corpo bruto é necessário para conferir o X-Hub-Signature-256.
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.get("x-hub-signature-256");
if (!isValidSignature(req.body, signature)) {
return res.sendStatus(401);
}
let payload;
try {
payload = JSON.parse(req.body.toString("utf8"));
} catch {
return res.sendStatus(400);
}
if (payload.object === "whatsapp_business_account") {
for (const entry of payload.entry ?? []) {
for (const change of entry.changes ?? []) {
for (const message of change.value?.messages ?? []) {
if (message.type === "text") {
console.log(`Mensagem de ${message.from}: ${message.text.body}`);
}
}
}
}
}
// Responda rápido. O processamento pesado deve ir para uma fila.
return res.sendStatus(200);
});
app.listen(PORT, () => {
console.log(`Webhook ouvindo na porta ${PORT}`);
});
2. Inicie o endpoint com variáveis protegidas
Não coloque token, App Secret ou credenciais dentro do código nem no repositório. No Linux ou macOS, o processo pode ser iniciado assim:
WA_VERIFY_TOKEN="crie-um-token-longo" \\
META_APP_SECRET="seu-app-secret" \\
PORT=3000 \\
node server.js
O token de verificação é uma senha que você escolhe e repete no painel da Meta. Ele não é o mesmo que o token de acesso usado para enviar mensagens.
3. Cadastre a URL no painel da Meta
- Abra o aplicativo no Meta for Developers e entre na configuração do WhatsApp.
- Procure a área de configuração de Webhooks e informe a URL pública terminada em
/webhook. - Digite exatamente o mesmo valor usado em
WA_VERIFY_TOKEN. - Conclua a verificação. Se tudo estiver certo, o painel envia o challenge e recebe o mesmo número de volta.
- Assine o campo
messagespara a conta WhatsApp Business. Sem essa assinatura, o endpoint pode estar online e ainda assim não receber mensagens.
Uma forma rápida de conferir apenas a etapa GET é abrir uma URL parecida com esta, trocando domínio e token:
curl -i "https://SEU-DOMINIO/webhook?hub.mode=subscribe&hub.verify_token=meu-token&hub.challenge=12345"
O retorno esperado é 12345. Se aparecer 403, confira os nomes dos parâmetros, o token e se a variável de ambiente foi carregada pelo processo.
4. Teste o recebimento de uma mensagem
Envie uma mensagem para o número de teste configurado na Meta e observe o terminal. Para um texto, o programa deve mostrar o telefone do remetente e o conteúdo. O payload oficial pode carregar vários objetos em entry, changes e messages; por isso o exemplo percorre arrays em vez de assumir que sempre existirá um único evento.
O servidor deve responder 200 OK rapidamente. A resposta significa que seu endpoint recebeu e aceitou o evento — não que uma eventual resposta ao cliente foi entregue. A confirmação de envio e entrega chega por webhooks de status.
Como responder pelo mesmo sistema
Para mandar uma resposta, use a Messages API com o Phone Number ID. O trecho abaixo funciona em versões atuais do Node.js que já têm fetch nativo. Ajuste a versão da Graph API conforme a documentação exibida no seu painel:
const version = process.env.META_GRAPH_VERSION || "v25.0";
const url = `https://graph.facebook.com/${version}/${process.env.WA_PHONE_NUMBER_ID}/messages`;
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WA_ACCESS_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
messaging_product: "whatsapp",
recipient_type: "individual",
to: "+5511999999999",
type: "text",
text: { body: "Recebi sua mensagem. Como posso ajudar?" }
})
});
console.log(await response.json());
Esse envio de texto livre depende da janela de atendimento: quando o usuário manda uma mensagem, a Meta abre uma janela de atendimento de 24 horas. Enquanto ela está aberta, você pode enviar mensagens de serviço sem template pré-aprovado. Depois, a comunicação iniciada pela empresa precisa usar um template aprovado e ainda deve respeitar opt-in e as políticas da plataforma.
Erros comuns e como corrigir
O painel não valida a URL
Confira se o endereço é HTTPS público, se a rota é exatamente /webhook e se o servidor responde ao GET sem exigir autenticação adicional. Certificados autoassinados e URLs acessíveis apenas em localhost não resolvem essa etapa.
O evento chega, mas retorna 401
Isso normalmente indica que o App Secret está errado, que o corpo foi convertido em JSON antes da validação ou que o cabeçalho não foi repassado pelo proxy. Por isso o código usa express.raw() nessa rota e calcula a assinatura sobre os bytes originais.
O servidor recebe status, mas não mensagens
Verifique se o campo messages foi assinado no Webhooks e se a conta correta está ligada ao aplicativo. Também confira se você está testando o número conectado à Cloud API, e não um número que continua apenas no aplicativo comum.
A resposta funciona no teste e falha depois
Veja a janela de 24 horas, o opt-in, o template e o status de qualidade da conta. A API aceitar uma requisição não garante que a mensagem foi entregue; guarde os IDs e acompanhe os webhooks de sent, delivered, read e failed.
Como levar o exemplo para produção
- coloque o processamento pesado em uma fila e responda ao webhook sem demora;
- grave o ID do evento e da mensagem para evitar duplicidade quando a Meta fizer uma nova tentativa;
- não registre tokens nem o texto completo de conversas sem uma política clara de retenção;
- trate mensagens que não sejam texto, erros no payload e mudanças de versão da API;
- separe o recebimento do evento do envio da resposta e monitore os status da Messages API.
Se você ainda está montando a base do servidor, este guia complementa o tutorial do JSMS sobre como criar uma API REST com Node.js e Express. A ideia é a mesma: expor uma rota HTTP; a diferença é que, aqui, a chamada nasce em um serviço externo e precisa ser autenticada.
Perguntas frequentes
Posso testar o webhook usando apenas localhost?
Não diretamente. A Meta precisa alcançar sua rota pela internet usando HTTPS. Um túnel de desenvolvimento pode encaminhar uma URL pública para o seu computador, mas não substitui a configuração segura de produção.
Preciso usar uma biblioteca não oficial do WhatsApp?
Não para esse fluxo. A Cloud API oficial recebe eventos por Webhooks e envia mensagens pela Messages API. Bibliotecas que automatizam o WhatsApp Web pertencem a outro modelo e podem ter limitações e riscos diferentes.
Por que a Meta envia mais de um webhook para a mesma mensagem?
Uma mensagem enviada pela empresa pode gerar eventos separados de envio, entrega, leitura ou falha. O sistema deve identificar cada evento pelo ID e não tratar uma nova tentativa como uma conversa nova.
Posso enviar qualquer resposta depois que o cliente fala comigo?
Não. A janela de atendimento de 24 horas permite mensagens de serviço sem template. Fora dela, use um template aprovado e observe as regras de opt-in, qualidade e categoria da mensagem.