Blog

Checklist de segurança para integrações Meta e WhatsApp

Integrar com a Meta e o WhatsApp Cloud API significa expor endpoints públicos, manipular tokens de longa duração e processar dados de pessoas reais. Cada um desses pontos é uma superfície de ataque. Este checklist reúne as práticas de AppSec que evitam que um webhook vire porta de entrada: verificação de assinatura, segregação e rotação de credenciais, rate limiting e trilha de auditoria. O foco é prático, com código que você pode colar e adaptar hoje.

2026-02-04 / Segurança / 10 min

01

Por que a segurança de webhooks da Meta exige atenção especial

Um webhook é um endpoint HTTP público. Qualquer pessoa na internet pode enviá-lo um POST forjado. Sem verificação de assinatura, seu sistema processaria mensagens falsas, eventos duplicados e payloads maliciosos como se fossem legítimos. A Meta resolve isso assinando cada requisição com HMAC SHA-256 usando o App Secret, e cabe a você validar essa assinatura antes de confiar em qualquer byte do corpo.

Além da assinatura, há três outras camadas que separam uma integração amadora de uma integração de produção: credenciais segregadas e rotacionáveis, limites de taxa para conter abuso e uma trilha de auditoria que permite reconstruir o que aconteceu sem vazar dados pessoais.

Internet  -->  [Edge / WAF]  -->  [Verificação HMAC]  -->  [Rate limit]  -->  [Handler]
                                |                    |                 |
                            bloqueia            rejeita 401       rejeita 429
                            payload bruto       assinatura        excedeu cota
                                                inválida

02

Verificação de assinatura do webhook (x-hub-signature-256)

A Meta envia o cabeçalho x-hub-signature-256 no formato sha256=<hex>. O valor é o HMAC SHA-256 do corpo bruto da requisição usando o App Secret como chave. Duas regras inegociáveis: use o body cru (raw), não o JSON já parseado e re-serializado, porque qualquer diferença de bytes muda o hash; e compare com timingSafeEqual para evitar ataques de timing.

const crypto = require('crypto');

// Capture o corpo cru antes de qualquer parser JSON.
// No Express: express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } })

function verifyMetaSignature(req) {
  const header = req.get('x-hub-signature-256');
  if (!header || !header.startsWith('sha256=')) return false;

  const expected = header.slice('sha256='.length);
  const hmac = crypto.createHmac('sha256', process.env.META_APP_SECRET);
  hmac.update(req.rawBody); // Buffer cru, nao JSON.stringify(req.body)
  const computed = hmac.digest('hex');

  const a = Buffer.from(computed, 'hex');
  const b = Buffer.from(expected, 'hex');

  // timingSafeEqual exige buffers do mesmo tamanho.
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

app.post('/webhook', (req, res) => {
  if (!verifyMetaSignature(req)) {
    return res.sendStatus(401); // assinatura invalida
  }
  res.sendStatus(200); // confirme rapido, processe em fila
  enqueue(req.body);
});

Note o padrão: responda 200 imediatamente após validar e empurre o processamento para uma fila. A Meta reentrega eventos não confirmados, então processamento síncrono e lento gera duplicatas e timeouts.

03

Segregação e rotação de credenciais

Tratar App Secret e tokens como segredos de verdade é o que evita o pior cenário: um vazamento de credencial com permissões amplas e sem expiração. Cada credencial deve ter escopo mínimo, origem clara e um plano de rotação.

  • App Secret: usado apenas para verificar assinaturas. Nunca o exponha em frontend, logs ou repositório. Trate-o como chave criptográfica.
  • System User token: prefira tokens de System User (longa duração e renováveis) a tokens de usuário pessoal, que quebram quando a pessoa sai da empresa.
  • Escopo mínimo: conceda apenas as permissões que a integração realmente usa (por exemplo whatsapp_business_messaging), nada de pedir tudo por conveniência.
  • Cofre de segredos: armazene em um secrets manager (AWS Secrets Manager, Vault, GCP Secret Manager). Nunca em .env versionado ou em variáveis de imagem Docker.
  • Rotação programada: defina um ciclo (por exemplo a cada 90 dias) e tenha um runbook para girar o App Secret e revogar tokens antigos sem downtime.
  • Segregação por ambiente: credenciais de produção, staging e dev devem ser distintas. Um vazamento em dev nunca pode comprometer produção.

04

Rate limiting e proteção contra abuso

Mesmo com assinatura válida, você precisa limitar a taxa por origem para conter picos, loops de reentrega e tentativas de exaustão de recursos. Aplique limites por IP na borda e por tenant na aplicação, usando um algoritmo de token bucket que tolera rajadas curtas mas corta abuso sustentado.

// Token bucket simples por chave (IP ou tenant).
class TokenBucket {
  constructor(capacity, refillPerSec) {
    this.capacity = capacity;
    this.refill = refillPerSec;
    this.tokens = capacity;
    this.last = Date.now();
  }
  allow(cost = 1) {
    const now = Date.now();
    this.tokens = Math.min(
      this.capacity,
      this.tokens + ((now - this.last) / 1000) * this.refill
    );
    this.last = now;
    if (this.tokens < cost) return false;
    this.tokens -= cost;
    return true;
  }
}

const buckets = new Map(); // em producao: Redis com TTL
function rateLimit(key) {
  if (!buckets.has(key)) buckets.set(key, new TokenBucket(60, 1));
  return buckets.get(key).allow();
}

Em escala, troque o Map em memória por Redis para que o limite seja consistente entre instâncias. Responda 429 com Retry-After quando a cota estourar.

05

Trilha de auditoria sem PII em claro

Uma boa trilha de auditoria responde quem fez o que, quando e a partir de onde, sem transformar o log em um depósito de dados pessoais. Registre identificadores e metadados, nunca o conteúdo da mensagem ou números de telefone em texto puro. Para correlacionar sem expor, use hash ou pseudonimização.

CampoO que registrarPII?
quemID do tenant, ID do app, sub do tokenNão (use IDs internos)
o queTipo do evento (ex: message.received), ação tomadaNão
quandoTimestamp UTC e ID de correlação do eventoNão
de ondeIP de origem (mascarado), user-agentParcial (mascare IP)
contatoHash do telefone (SHA-256 + salt), nunca o númeroNão se hasheado
resultadoStatus (ok, rejeitado, 401, 429) e motivoNão

Defina retenção explícita para os logs e garanta que eles sejam imutáveis (append-only). Sob LGPD e GDPR, log não é desculpa para reter dados pessoais indefinidamente.

06

LGPD e GDPR: consentimento e minimização

WhatsApp envolve dados pessoais por definição. Dois princípios guiam a conformidade no contexto de integrações: base legal e consentimento para iniciar conversas (especialmente mensagens de marketing), e minimização de dados, ou seja, colete e armazene apenas o estritamente necessário para a finalidade declarada.

  • Consentimento: tenha registro de opt-in antes de enviar mensagens proativas e respeite o opt-out imediatamente.
  • Minimização: não persista o corpo das mensagens se a finalidade não exige; prefira processar e descartar.
  • Direitos do titular: tenha um caminho para exclusão e portabilidade dos dados quando solicitado.

07

Checklist final acionavel

  1. Capturar o corpo cru e validar x-hub-signature-256 com HMAC SHA-256 e timingSafeEqual antes de processar.
  2. Responder 200 rápido e processar em fila, com idempotência por ID de evento.
  3. Mover App Secret e tokens para um secrets manager, fora de qualquer arquivo versionado.
  4. Usar System User token com escopo mínimo e separar credenciais por ambiente.
  5. Definir e testar um ciclo de rotação de credenciais com runbook sem downtime.
  6. Aplicar rate limiting por IP na borda e por tenant na aplicação (token bucket + Redis).
  7. Registrar trilha de auditoria append-only com IDs e hashes, sem PII em claro, com retenção definida.
  8. Documentar base legal, opt-in/opt-out e política de minimização para LGPD e GDPR.

FAQ

Perguntas frequentes

Posso usar JSON.stringify do corpo para calcular o HMAC?

Não. A assinatura é calculada sobre os bytes exatos enviados pela Meta. Re-serializar o JSON muda espaços, ordem e escaping, gerando um hash diferente. Capture o raw body com um verify do parser e use esse Buffer.

Por que comparar a assinatura com timingSafeEqual em vez de ===?

Comparações de string normais retornam mais rápido quando os primeiros caracteres divergem, o que vaza informação por timing e permite reconstruir a assinatura tentativa a tentativa. timingSafeEqual compara em tempo constante, fechando esse canal lateral.

Onde devo guardar o App Secret em produção?

Em um secrets manager dedicado (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager) com acesso por IAM e rotação programada. Nunca em .env commitado, em variáveis de build da imagem Docker ou em qualquer lugar acessível pelo frontend.

Segurança de integração não é opcional

Assinatura verificada, credenciais segregadas, limites de taxa e auditoria limpa formam a base de uma integração Meta e WhatsApp que aguenta produção e auditoria. Comece pelo checklist final e feche cada lacuna antes de ir ao ar.