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>
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
- Acesse developers.facebook.com
- Selecione seu App
- No menu lateral: WhatsApp > Configuration
- 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 deres.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
- Copie a URL HTTPS do ngrok (ex:
https://a1b2c3d4.ngrok-free.app) - No painel da Meta, atualize o Callback URL para:
https://a1b2c3d4.ngrok-free.app/webhook - 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=stdoutpara ver logs no terminal - O Inspector web (
http://127.0.0.1:4040) permite replay de requests - Adicione um endpoint
/healthpara 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