Files
ProgramadorBrasil 61ec71c5c7 feat: add 52 specialized AI agent skills (#217)
New skills covering 10 categories:

**Security & Audit**: 007 (STRIDE/PASTA/OWASP), cred-omega (secrets management)
**AI Personas**: Karpathy, Hinton, Sutskever, LeCun (4 sub-skills), Altman, Musk, Gates, Jobs, Buffett
**Multi-agent Orchestration**: agent-orchestrator, task-intelligence, multi-advisor
**Code Analysis**: matematico-tao (Terence Tao-inspired mathematical code analysis)
**Social & Messaging**: Instagram Graph API, Telegram Bot, WhatsApp Cloud API, social-orchestrator
**Image Generation**: AI Studio (Gemini), Stability AI, ComfyUI Gateway, image-studio router
**Brazilian Domain**: 6 auction specialist modules, 2 legal advisors, auctioneers data scraper
**Product & Growth**: design, invention, monetization, analytics, growth engine
**DevOps & LLM Ops**: Docker/CI-CD/AWS, RAG/embeddings/fine-tuning
**Skill Governance**: installer, sentinel auditor, context management

Each skill includes:
- Standardized YAML frontmatter (name, description, risk, source, tags, tools)
- Structured sections (Overview, When to Use, How it Works, Best Practices)
- Python scripts and reference documentation where applicable
- Cross-platform compatibility (Claude Code, Antigravity, Cursor, Gemini CLI, Codex CLI)

Co-authored-by: ProgramadorBrasil <214873561+ProgramadorBrasil@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:04:07 +01:00

17 KiB

Configuracao de Webhooks - WhatsApp Cloud API

Guia completo para configurar, validar e proteger webhooks da WhatsApp Cloud API.


1. Visao Geral

Webhooks sao callbacks HTTP que a Meta envia para o seu servidor sempre que um evento ocorre na sua conta do WhatsApp Business. Sem webhooks, voce nao recebe mensagens, confirmacoes de entrega nem atualizacoes de status em tempo real.

Requisitos obrigatorios:

Requisito Detalhe
Protocolo HTTPS com certificado SSL valido (nao aceita auto-assinado)
Resposta HTTP 200 OK em ate 5 segundos
Disponibilidade Endpoint deve estar acessivel publicamente na internet
Idempotencia A Meta pode reenviar o mesmo evento; trate duplicatas

Se o seu servidor nao responder 200 dentro de 5 segundos, a Meta reenvia o evento com backoff exponencial por ate 7 dias. Apos esse periodo, o webhook e desativado automaticamente.


2. Configuracao no Meta Developers

Passo a passo

  1. Acesse developers.facebook.com
  2. Selecione seu App
  3. No menu lateral: WhatsApp > Configuration
  4. Na secao Webhook, clique em Edit

Campos obrigatorios

Campo Descricao
Callback URL URL HTTPS do seu servidor (ex: https://api.seudominio.com/webhook)
Verify Token String secreta que voce define (ex: meu_token_secreto_2024)

Campos para inscrever (Webhook Fields)

Marque pelo menos:

  • messages - Mensagens recebidas, status de entrega, leitura

Campos opcionais uteis:

  • message_template_status_update - Aprovacao/rejeicao de templates
  • account_update - Alteracoes na conta Business

IMPORTANTE: O Verify Token NAO e o mesmo que o Access Token da API. Escolha um valor forte e unico, e armazene-o como variavel de ambiente.


3. Verificacao de Webhook (GET)

Quando voce salva a configuracao no painel da Meta, ela envia um GET request para validar que o endpoint pertence a voce. Esse fluxo e chamado de challenge-response.

Fluxo de verificacao

Meta                            Seu Servidor
  |                                  |
  |  GET /webhook?                   |
  |    hub.mode=subscribe            |
  |    hub.verify_token=SEU_TOKEN    |
  |    hub.challenge=RANDOM_STRING   |
  |  ---------------------------->>  |
  |                                  |  1. Verifica hub.verify_token
  |                                  |  2. Se valido, retorna hub.challenge
  |  <<----------------------------  |
  |  HTTP 200 + challenge como body  |

Node.js / Express

// GET /webhook - Verification endpoint
app.get('/webhook', (req, res) => {
  const VERIFY_TOKEN = process.env.WEBHOOK_VERIFY_TOKEN;

  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 verified successfully');
    return res.status(200).send(challenge);
  }

  console.error('Webhook verification failed: invalid token');
  return res.sendStatus(403);
});

Python / Flask

# GET /webhook - Verification endpoint
@app.route('/webhook', methods=['GET'])
def verify_webhook():
    verify_token = os.environ.get('WEBHOOK_VERIFY_TOKEN')

    mode = request.args.get('hub.mode')
    token = request.args.get('hub.verify_token')
    challenge = request.args.get('hub.challenge')

    if mode == 'subscribe' and token == verify_token:
        print('Webhook verified successfully')
        return challenge, 200

    print('Webhook verification failed: invalid token')
    return 'Forbidden', 403

Erros comuns na verificacao

Erro Causa Solucao
403 Forbidden Verify token nao confere Verifique a variavel de ambiente
Webhook nao valida Challenge retornado como JSON Retorne como plain text, nao JSON
Timeout Servidor demorou mais de 5s Verifique latencia e middleware
SSL Error Certificado invalido ou expirado Use Let's Encrypt ou certificado valido

Erro classico: Retornar res.json({ challenge }) em vez de res.send(challenge). A Meta espera o challenge como texto puro no body da resposta.


4. Recebimento de Mensagens (POST)

Apos a verificacao, a Meta envia eventos via POST. Cada payload segue a mesma estrutura base, mas o conteudo varia conforme o tipo de evento.

Node.js / Express - Handler completo

// POST /webhook - Receive events
app.post('/webhook', (req, res) => {
  // SEMPRE responda 200 imediatamente
  res.sendStatus(200);

  const body = req.body;

  if (!body.object || !body.entry) return;

  for (const entry of body.entry) {
    for (const change of entry.changes) {
      if (change.field !== 'messages') continue;

      const value = change.value;
      const metadata = value.metadata;
      const phoneNumberId = metadata.phone_number_id;

      // Status updates (sent, delivered, read, failed)
      if (value.statuses) {
        for (const status of value.statuses) {
          handleStatusUpdate(status);
        }
      }

      // Incoming messages
      if (value.messages) {
        for (const message of value.messages) {
          const from = message.from;
          const timestamp = message.timestamp;

          switch (message.type) {
            case 'text':
              handleTextMessage(from, message.text.body, phoneNumberId);
              break;
            case 'image':
            case 'video':
            case 'audio':
            case 'document':
              handleMediaMessage(from, message.type, message[message.type]);
              break;
            case 'interactive':
              handleInteractiveResponse(from, message.interactive);
              break;
            case 'button':
              handleButtonResponse(from, message.button);
              break;
            case 'location':
              handleLocationMessage(from, message.location);
              break;
            default:
              console.log(`Unhandled message type: ${message.type}`);
          }
        }
      }
    }
  }
});

Python / Flask - Handler completo

# POST /webhook - Receive events
@app.route('/webhook', methods=['POST'])
def receive_webhook():
    body = request.get_json()

    if not body or 'entry' not in body:
        return 'OK', 200

    for entry in body.get('entry', []):
        for change in entry.get('changes', []):
            if change.get('field') != 'messages':
                continue

            value = change.get('value', {})
            metadata = value.get('metadata', {})
            phone_number_id = metadata.get('phone_number_id')

            # Status updates
            for status in value.get('statuses', []):
                handle_status_update(status)

            # Incoming messages
            for message in value.get('messages', []):
                sender = message['from']
                msg_type = message['type']

                if msg_type == 'text':
                    handle_text_message(sender, message['text']['body'], phone_number_id)
                elif msg_type in ('image', 'video', 'audio', 'document'):
                    handle_media_message(sender, msg_type, message[msg_type])
                elif msg_type == 'interactive':
                    handle_interactive_response(sender, message['interactive'])
                elif msg_type == 'button':
                    handle_button_response(sender, message['button'])
                elif msg_type == 'location':
                    handle_location_message(sender, message['location'])

    return 'OK', 200

Exemplos de payload por tipo de evento

Mensagem de texto recebida:

{
  "messages": [{
    "from": "5511999887766",
    "id": "wamid.HBgNNTUxMTk5OTg...",
    "timestamp": "1677000000",
    "type": "text",
    "text": { "body": "Ola, preciso de ajuda" }
  }]
}

Resposta de botao interativo (list/button reply):

{
  "messages": [{
    "from": "5511999887766",
    "type": "interactive",
    "interactive": {
      "type": "button_reply",
      "button_reply": {
        "id": "btn_confirm",
        "title": "Confirmar pedido"
      }
    }
  }]
}

Atualizacao de status (entrega):

{
  "statuses": [{
    "id": "wamid.HBgNNTUxMTk5OTg...",
    "status": "delivered",
    "timestamp": "1677000030",
    "recipient_id": "5511999887766"
  }]
}

5. Seguranca HMAC-SHA256 (CRITICO)

Por que e essencial

Sem validacao de assinatura, qualquer pessoa que descubra a URL do seu webhook pode enviar payloads falsos. Isso permite:

  • Spoofing de mensagens - Simular que um cliente enviou algo que nunca enviou
  • Execucao de comandos - Se o webhook dispara acoes (pagamentos, envios), atacantes controlam
  • Exfiltracao de dados - Payloads maliciosos podem explorar falhas de parsing

Incidente real documentado: Uma empresa de e-commerce sofreu prejuizo de US$ 847 mil apos atacantes enviarem payloads falsos de "confirmacao de pagamento" para o webhook que nao validava assinatura, disparando envios de mercadoria sem pagamento real.

Como funciona

A cada request POST, a Meta inclui o header X-Hub-Signature-256 contendo:

sha256=<hmac-sha256-hex-digest>

O HMAC e calculado usando o App Secret como chave e o raw body como mensagem.

Passo a passo da validacao

1. Capture o raw body ANTES do JSON parsing
2. Extraia o header X-Hub-Signature-256
3. Compute HMAC-SHA256(app_secret, raw_body)
4. Compare usando funcao constant-time (previne timing attack)
5. Se nao bater, rejeite com 401

Node.js / Express - Middleware de validacao

const crypto = require('crypto');

function validateWebhookSignature(req, res, next) {
  const APP_SECRET = process.env.META_APP_SECRET;

  // CRITICO: raw body deve ser capturado ANTES do json parser
  // Configure o Express assim:
  // app.use(express.json({
  //   verify: (req, _res, buf) => { req.rawBody = buf; }
  // }));

  const signature = req.headers['x-hub-signature-256'];

  if (!signature) {
    console.error('Missing X-Hub-Signature-256 header');
    return res.sendStatus(401);
  }

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

  const signatureBuffer = Buffer.from(signature);
  const expectedBuffer = Buffer.from(expectedSignature);

  if (signatureBuffer.length !== expectedBuffer.length ||
      !crypto.timingSafeEqual(signatureBuffer, expectedBuffer)) {
    console.error('Invalid webhook signature');
    return res.sendStatus(401);
  }

  next();
}

// Uso:
// app.post('/webhook', validateWebhookSignature, webhookHandler);

Python / Flask - Decorator de validacao

import hmac
import hashlib
from functools import wraps

def validate_webhook_signature(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        app_secret = os.environ.get('META_APP_SECRET')

        # CRITICO: raw body ANTES do JSON parsing
        raw_body = request.get_data()

        signature = request.headers.get('X-Hub-Signature-256', '')

        if not signature:
            print('Missing X-Hub-Signature-256 header')
            return 'Unauthorized', 401

        expected = 'sha256=' + hmac.new(
            app_secret.encode('utf-8'),
            raw_body,
            hashlib.sha256
        ).hexdigest()

        if not hmac.compare_digest(signature, expected):
            print('Invalid webhook signature')
            return 'Unauthorized', 401

        return f(*args, **kwargs)
    return decorated

# Uso:
# @app.route('/webhook', methods=['POST'])
# @validate_webhook_signature
# def receive_webhook():
#     ...

Erro classico: usar body parseado

// ERRADO - body ja foi parseado para JSON, altera o conteudo
const hmac = crypto.createHmac('sha256', secret)
  .update(JSON.stringify(req.body))  // NAO FACA ISSO
  .digest('hex');

// CORRETO - usar o raw body original
const hmac = crypto.createHmac('sha256', secret)
  .update(req.rawBody)  // Buffer original do request
  .digest('hex');

Por que falha: JSON.stringify(JSON.parse(raw)) pode produzir output diferente do raw original (espacamento, ordem de chaves, encoding de caracteres Unicode). A assinatura da Meta foi calculada sobre o raw body exato.


6. Desenvolvimento Local

Para testar webhooks localmente, voce precisa expor seu servidor local para a internet. A ferramenta mais usada para isso e o ngrok.

Instalacao e uso do ngrok

# Instalar (macOS)
brew install ngrok

# Instalar (Windows via Chocolatey)
choco install ngrok

# Instalar (Linux)
snap install ngrok

# Autenticar (necessario uma vez)
ngrok config add-authtoken SEU_AUTH_TOKEN

# Expor porta local 3000
ngrok http 3000

Saida do ngrok

Session Status   online
Forwarding       https://a1b2c3d4.ngrok-free.app -> http://localhost:3000

Configurar no Meta Developers

  1. Copie a URL HTTPS do ngrok (ex: https://a1b2c3d4.ngrok-free.app)
  2. No painel da Meta, atualize o Callback URL para: https://a1b2c3d4.ngrok-free.app/webhook
  3. Salve e valide

ATENCAO: A URL do ngrok muda a cada reinicio (no plano gratuito). Voce precisara atualizar no painel da Meta toda vez que reiniciar o ngrok.

Debugging de payloads

# O painel web do ngrok mostra todos os requests
# Acesse: http://127.0.0.1:4040

# Alternativa: log detalhado no servidor
app.post('/webhook', (req, res) => {
  console.log('Headers:', JSON.stringify(req.headers, null, 2));
  console.log('Body:', JSON.stringify(req.body, null, 2));
  res.sendStatus(200);
});

Dicas para desenvolvimento local

  • Use ngrok http 3000 --log=stdout para ver logs no terminal
  • O Inspector web (http://127.0.0.1:4040) permite replay de requests
  • Adicione um endpoint /health para verificar rapidamente se o servidor esta no ar
  • Considere usar localtunnel como alternativa gratuita ao ngrok

7. Deploy em Producao

Requisitos de certificado HTTPS

  • Certificado SSL valido emitido por CA reconhecida
  • Let's Encrypt e aceito e gratuito
  • Certificados auto-assinados NAO sao aceitos
  • Certifique-se de que a cadeia completa (chain) esta configurada
  • Configure renovacao automatica (certbot renew via cron)

Retry logic e idempotencia

A Meta reenvia eventos com backoff exponencial quando nao recebe HTTP 200:

Tentativa Intervalo aproximado
1a Imediato
2a ~1 minuto
3a ~5 minutos
4a ~30 minutos
Seguintes Backoff crescente ate 7 dias

Implemente idempotencia:

const processedMessages = new Set(); // Em producao, use Redis

function isNewMessage(messageId) {
  if (processedMessages.has(messageId)) {
    return false;
  }
  processedMessages.add(messageId);

  // Limpar mensagens antigas apos 24h (em producao, use TTL do Redis)
  setTimeout(() => processedMessages.delete(messageId), 86400000);
  return true;
}

// No handler:
if (!isNewMessage(message.id)) {
  console.log(`Duplicate message ${message.id}, skipping`);
  return;
}

Scaling e capacidade

A Meta recomenda que o seu servidor suporte:

Metrica Recomendacao
Capacidade de entrada 3x o volume de mensagens enviadas + 1x mensagens recebidas
Tempo de resposta < 5 segundos (idealmente < 1 segundo)
Disponibilidade 99.9% uptime minimo

Arquitetura recomendada para alto volume:

[Meta Webhook] --> [Load Balancer]
                        |
                   [Web Server]  --> Responde 200 imediatamente
                        |
                   [Message Queue] (Redis/SQS/RabbitMQ)
                        |
                   [Workers]  --> Processamento assincrono

Monitoramento de saude do webhook

// Endpoint de health check
app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    uptime: process.uptime(),
    timestamp: new Date().toISOString()
  });
});

// Metricas essenciais para monitorar:
// - Taxa de erros 4xx/5xx no endpoint /webhook
// - Latencia media de resposta (deve ser < 1s)
// - Numero de mensagens duplicadas recebidas
// - Fila de processamento (tamanho e tempo medio)
// - Falhas de validacao HMAC (possivel ataque)

Alertas recomendados:

Alerta Threshold Acao
Latencia alta > 3 segundos Investigar gargalos, escalar workers
Taxa de erro > 1% Verificar logs, possivel bug no handler
Falha HMAC > 0 por hora Possivel ataque; verificar APP_SECRET
Fila crescendo > 1000 mensagens Escalar workers de processamento
Webhook desativado Alerta da Meta Verificar SSL e disponibilidade

Checklist Final

  • Endpoint acessivel via HTTPS com certificado valido
  • Verificacao GET retorna challenge como plain text
  • Handler POST responde 200 em menos de 5 segundos
  • Validacao HMAC-SHA256 implementada com raw body
  • Comparacao constant-time (timingSafeEqual / compare_digest)
  • Idempotencia para mensagens duplicadas
  • Processamento assincrono para operacoes demoradas
  • Monitoramento e alertas configurados
  • APP_SECRET e VERIFY_TOKEN em variaveis de ambiente (nunca no codigo)
  • Logs estruturados para debugging em producao