Blog

Guia WhatsApp Cloud API: arquitetura, webhooks, templates e deploy

Um guia de produção para integrar a WhatsApp Cloud API: quando usá-la em vez de soluções não oficiais, a arquitetura recomendada (webhook, fila, worker, CRM/IA, observabilidade), webhook seguro com verificação HMAC e idempotência, governança de templates e um checklist de deploy.

2026-03-03 / WhatsApp Cloud API / 14 min

01

Cloud API vs solução não oficial: quando escolher cada uma

A WhatsApp Cloud API é a API oficial hospedada pela Meta. Você fala com endpoints em graph.facebook.com, recebe eventos por webhook e opera dentro das políticas da plataforma. Soluções não oficiais (libs que automatizam o WhatsApp Web) prometem custo zero de conversa, mas operam fora dos termos e quebram a cada atualização do cliente.

A regra prática: se o número representa uma empresa, processa volume relevante ou precisa de previsibilidade, use a Cloud API. O risco de banimento de uma solução não oficial não é teórico, é uma questão de quando, e o prejuízo de perder o número principal supera de longe a economia de mensagem.

CritérioCloud API (oficial)Não oficial
EstabilidadeAlta, contrato de API versionadoQuebra a cada update do WhatsApp Web
Risco de banBaixo dentro das políticasAlto, viola os termos de uso
EscalaThroughput negociável (tiers)Limitado por sessão única
Templates e botõesSuporte nativoParcial ou frágil
CustoPor conversa iniciadaAparentemente zero, risco oculto
SLA e suporteBSP e MetaComunidade, sem garantia

Use não oficial apenas em provas de conceito descartáveis ou números secundários que você pode perder sem impacto. Para qualquer fluxo que sustente receita ou atendimento, Cloud API.

02

Arquitetura recomendada de produção

O erro mais comum é processar o webhook de forma síncrona: receber o POST, chamar a IA, gravar no CRM e só então responder 200 à Meta. Isso acopla a latência do seu pipeline ao timeout do webhook. A Meta espera resposta rápida e reentrega o evento se você demorar, gerando duplicatas. A solução é desacoplar: o endpoint só valida, persiste e enfileira; o trabalho pesado roda em workers.

  WhatsApp / Meta
        |
        v  POST (x-hub-signature-256)
  +-----------------+
  | Webhook (HTTP)  |  valida HMAC + idempotencia
  +-----------------+
        | enfileira (event_id)
        v
  +-----------------+
  |  Fila (Redis /  |
  |  SQS / Rabbit)  |
  +-----------------+
        | consome
        v
  +-----------------+      +-------------+
  |     Worker      |----->|  IA / RAG   |
  | (lógica, retry) |<-----|  (intent)   |
  +-----------------+      +-------------+
        |        \
        v         v
  +---------+   +-------------------+
  |  CRM /  |   | Observabilidade   |
  |  ERP    |   | (logs, métricas,  |
  +---------+   | traces, alertas)  |
                +-------------------+
  • Webhook: superfície fina, sem regra de negócio. Valida assinatura, deduplica por message id e responde 200 em milissegundos.
  • Fila: absorve picos de campanha e isola falhas do worker do recebimento. Reentrega controlada com backoff, não pela Meta.
  • Worker: idempotente, com retry e dead-letter queue. Aqui vive a orquestração com IA, CRM e regras.
  • CRM/IA: integrações externas tratadas como instáveis. Timeout curto, circuit breaker e fallback para handoff humano.
  • Observabilidade: correlação por message id em todo o caminho, métricas de latência por etapa e alertas sobre taxa de erro e profundidade da fila.

03

Webhook seguro: assinatura HMAC e idempotência

Todo POST da Meta traz o header x-hub-signature-256, um HMAC SHA-256 do corpo bruto usando o App Secret. Você precisa validar isso sobre o body cru (não o JSON já parseado), com comparação em tempo constante. Sem essa verificação, qualquer um que descubra a URL pode injetar eventos falsos.

O segundo pilar é idempotência. A Meta pode reentregar o mesmo evento (timeout, retry interno). Cada mensagem traz um id único; registre-o em um store com TTL antes de enfileirar e descarte duplicatas. Assim o mesmo evento nunca dispara duas respostas ou duas escritas no CRM.

// server.js - webhook WhatsApp Cloud API (Node.js / Express)
const express = require('express');
const crypto = require('crypto');
const Redis = require('ioredis');

const redis = new Redis(process.env.REDIS_URL);
const APP_SECRET = process.env.WHATSAPP_APP_SECRET;
const VERIFY_TOKEN = process.env.WHATSAPP_VERIFY_TOKEN;

const app = express();

// IMPORTANTE: capturar o corpo BRUTO para validar o HMAC.
// O JSON re-serializado nao bate byte a byte com o assinado pela Meta.
app.use(
  express.json({
    verify: (req, _res, buf) => {
      req.rawBody = buf;
    },
  })
);

// Comparacao em tempo constante para evitar timing attacks.
function isValidSignature(req) {
  const header = req.get('x-hub-signature-256');
  if (!header || !req.rawBody) return false;

  const expected =
    'sha256=' +
    crypto.createHmac('sha256', APP_SECRET).update(req.rawBody).digest('hex');

  const a = Buffer.from(header);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Idempotencia: registra o message id com TTL; retorna false se ja visto.
async function markIfNew(messageId) {
  // SET key value NX EX 86400 -> grava so se nao existir, expira em 24h.
  const result = await redis.set(`wa:msg:${messageId}`, '1', 'EX', 86400, 'NX');
  return result === 'OK';
}

// Handshake de verificacao (GET) exigido pela Meta ao configurar a URL.
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);
});

// Recebimento de eventos (POST).
app.post('/webhook', async (req, res) => {
  if (!isValidSignature(req)) {
    return res.sendStatus(401);
  }

  // Responde rapido: a Meta reentrega se houver demora. Processamento e assincrono.
  res.sendStatus(200);

  try {
    const entries = req.body.entry || [];
    for (const entry of entries) {
      for (const change of entry.changes || []) {
        const messages = change.value?.messages || [];
        for (const message of messages) {
          const isNew = await markIfNew(message.id);
          if (!isNew) continue; // duplicata: ignora

          // Enfileira para o worker. O endpoint nao executa regra de negocio.
          await redis.lpush(
            'wa:incoming',
            JSON.stringify({
              messageId: message.id,
              from: message.from,
              type: message.type,
              text: message.text?.body,
              timestamp: message.timestamp,
            })
          );
        }
      }
    }
  } catch (err) {
    // Ja respondemos 200; logamos e deixamos o reprocessamento a cargo do worker/DLQ.
    console.error('webhook processing error', err);
  }
});

app.listen(process.env.PORT || 3000);

04

Templates, opt-in e qualidade do número

Fora da janela de 24 horas de atendimento, você só pode iniciar conversa com Message Templates aprovados pela Meta. Categorize corretamente (marketing, utility, authentication): a categoria afeta custo e nível de escrutínio. Templates de marketing enviados sem opt-in claro derrubam o quality rating do número e podem disparar limitação de envio.

AspectoBoa práticaRisco se ignorar
Opt-inConsentimento explícito e registrado por usuárioBloqueios em massa, queda de qualidade
CategoriaClassificar utility vs marketing corretamenteRejeição do template ou cobrança errada
Janela 24hTexto livre só dentro da sessão abertaMensagem não entregue fora da janela
Quality ratingMonitorar verde/amarelo/vermelho via APIRebaixamento de tier e limite de envio
VariáveisValidar placeholders {{1}} antes de enviarFalha de render ou reprovação na revisão
FrequênciaRespeitar cadência e botão de descadastroMarcação como spam pelos usuários

Trate o quality rating como um SLO. Consulte-o periodicamente, registre quedas e correlacione com campanhas recentes. Um número rebaixado para tier inferior limita quantos usuários novos você alcança por dia, o que pode travar uma operação inteira em pleno horário de pico.

05

Worker idempotente e tratamento de falhas

O worker consome a fila e executa a orquestração: classificar intenção (com IA ou regras), buscar contexto no CRM, gerar resposta e enviar via API de mensagens. Cada passo pode falhar de forma independente, então o worker precisa ser idempotente e ter política de retry com backoff, além de uma dead-letter queue para eventos que esgotam tentativas.

  1. Despacha a mensagem da fila e revalida idempotência pela chave de processamento (não só de recebimento).
  2. Classifica intenção e decide o caminho: resposta automática, busca em base de conhecimento ou handoff humano.
  3. Chama integrações externas com timeout curto e circuit breaker; falha ali não pode travar a fila inteira.
  4. Envia a resposta via Graph API e persiste o resultado no CRM dentro de uma operação idempotente.
  5. Em erro recuperável, reenfileira com backoff exponencial; após N tentativas, move para a DLQ e alerta.

O envio de mensagem em si também pode falhar (rate limit, número inválido, janela fechada). Trate o retorno da API: distinga erros transitórios (retry) de permanentes (descarta e registra). Nunca faça retry cego de um erro permanente, isso só queima quota e polui a fila.

06

Checklist de deploy

Antes de apontar a URL de webhook para produção, percorra esta sequência. A ordem importa: segredos e validação primeiro, depois conectividade, por último o tráfego real.

  1. App Secret, Verify Token e Access Token armazenados em secret manager, nunca em código ou repositório.
  2. HTTPS válido na URL de webhook (a Meta exige TLS) e handshake GET respondendo com o hub.challenge.
  3. Validação de x-hub-signature-256 ativa e testada com um payload assinado real, rejeitando assinatura inválida.
  4. Idempotência funcionando: reenviar o mesmo evento não gera resposta nem escrita duplicada.
  5. Fila e workers provisionados com escala mínima dimensionada para pico de campanha, não para a média.
  6. Dead-letter queue configurada e com alerta quando recebe eventos.
  7. Observabilidade: logs correlacionados por message id, métricas de latência por etapa e profundidade da fila.
  8. Templates aprovados, categorizados e com opt-in registrado para os fluxos que iniciam conversa.
  9. Alertas sobre queda do quality rating e sobre erros de envio acima do limiar.
  10. Plano de rollback e runbook de incidente: como pausar envios e drenar a fila com segurança.

FAQ

Perguntas frequentes

Preciso de um BSP (Business Solution Provider) para usar a Cloud API?

Não obrigatoriamente. A Cloud API pode ser usada direto via Meta, com onboarding pelo Meta Business e WhatsApp Manager. Um BSP agrega faturamento consolidado, suporte e ferramentas, mas você consegue rodar em produção sem ele, especialmente em operações de menor porte.

Por que validar o HMAC sobre o corpo bruto e não sobre o JSON parseado?

Porque a assinatura é calculada sobre os bytes exatos enviados pela Meta. Ao parsear e re-serializar o JSON, a ordem das chaves e a formatação podem mudar, e o HMAC recalculado não bate. Por isso capturamos o rawBody no momento do parse e validamos sobre ele.

O que acontece se eu demorar para responder 200 ao webhook?

A Meta considera o evento não entregue e o reenvia, gerando duplicatas. Por isso o endpoint deve responder 200 imediatamente após validar e enfileirar, deixando o processamento pesado para o worker. A idempotência protege contra as reentregas que ainda assim ocorrerem.

Quer essa arquitetura rodando no seu número?

Implemento integrações WhatsApp Cloud API de ponta a ponta: webhook seguro, filas, workers idempotentes, orquestração com IA e observabilidade. Se você precisa migrar de uma solução não oficial ou estruturar do zero, vamos conversar.