Webhooks
Webhooks permitem que sua aplicação receba notificações em tempo real sobre eventos que ocorrem no WhatsApp.
Como Funcionam
- Você configura uma URL no seu servidor para receber eventos
- Quando um evento ocorre (nova mensagem, status, etc), o ZapFlare envia uma requisição POST para sua URL
- Sua aplicação processa o evento e responde com status 200 OK
Configurando Webhooks
Durante a Criação da Instância
POST /v1/whatsapp/instances
{
"name": "Minha Instância",
"webhookUrl": "https://meu-site.com/webhook/whatsapp",
"webhookEvents": [
"messages.upsert",
"messages.update",
"connection.update"
]
}Atualizando Webhook Existente
POST /v1/whatsapp/instances/{instanceId}/webhook
{
"url": "https://meu-site.com/webhook/whatsapp",
"events": [
"messages.upsert",
"connection.update"
]
}Eventos Disponíveis
messages.upsert
Disparado quando uma nova mensagem é recebida ou enviada.
{
"event": "messages.upsert",
"instanceId": "inst_abc123",
"timestamp": "2024-01-15T15:30:00Z",
"data": {
"messageId": "BAE5F2A6C8B1E",
"from": "5511999999999",
"to": "5511888888888",
"fromMe": false,
"type": "text",
"text": "Olá, como posso ajudar?",
"timestamp": "2024-01-15T15:30:00Z"
}
}messages.update
Disparado quando uma mensagem é atualizada (status de entrega, leitura, etc).
{
"event": "messages.update",
"instanceId": "inst_abc123",
"timestamp": "2024-01-15T15:30:02Z",
"data": {
"messageId": "BAE5F2A6C8B1E",
"status": "delivered",
"timestamps": {
"sent": "2024-01-15T15:30:00Z",
"delivered": "2024-01-15T15:30:02Z"
}
}
}messages.delete
Disparado quando uma mensagem é deletada.
{
"event": "messages.delete",
"instanceId": "inst_abc123",
"timestamp": "2024-01-15T15:35:00Z",
"data": {
"messageId": "BAE5F2A6C8B1E",
"deletedBy": "5511999999999",
"forEveryone": true
}
}connection.update
Disparado quando o status da conexão muda.
{
"event": "connection.update",
"instanceId": "inst_abc123",
"timestamp": "2024-01-15T15:40:00Z",
"data": {
"status": "connected",
"phoneNumber": "5511999999999"
}
}qr.update
Disparado quando um novo QR Code é gerado.
{
"event": "qr.update",
"instanceId": "inst_abc123",
"timestamp": "2024-01-15T15:00:00Z",
"data": {
"qrCode": "data:image/png;base64,iVBORw0KGgoAAAANS...",
"expiresAt": "2024-01-15T15:01:00Z"
}
}Segurança
Assinatura HMAC
Todos os webhooks são assinados com HMAC-SHA256 para garantir autenticidade.
Header de Assinatura
X-Hub-Signature-256: sha256=abc123def456...Validando a Assinatura
const crypto = require('crypto');
function validateWebhook(body, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
const expected = `sha256=${expectedSignature}`;
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express middleware
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-hub-signature-256'];
const secret = 'seu_webhook_secret';
if (!validateWebhook(req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log('Evento recebido:', event);
// Processar evento...
res.status(200).send('OK');
});import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
def validate_webhook(body, signature, secret):
expected = hmac.new(
secret.encode(),
body,
hashlib.sha256
).hexdigest()
expected_signature = f'sha256={expected}'
return hmac.compare_digest(signature, expected_signature)
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Hub-Signature-256')
secret = 'seu_webhook_secret'
if not validate_webhook(request.data, signature, secret):
abort(401, 'Invalid signature')
event = request.json
print('Evento recebido:', event)
# Processar evento...
return 'OK', 200<?php
function validateWebhook($body, $signature, $secret) {
$expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
return hash_equals($signature, $expected);
}
// Receber webhook
$body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
$secret = 'seu_webhook_secret';
if (!validateWebhook($body, $signature, $secret)) {
http_response_code(401);
die('Invalid signature');
}
$event = json_decode($body, true);
error_log('Evento recebido: ' . print_r($event, true));
// Processar evento...
http_response_code(200);
echo 'OK';
?>Implementando um Servidor de Webhook
Exemplo Completo com Express
const express = require('express');
const crypto = require('crypto');
const app = express();
// Middleware para validar assinatura
const validateSignature = (secret) => {
return (req, res, next) => {
const signature = req.headers['x-hub-signature-256'];
if (!signature) {
return res.status(401).send('Missing signature');
}
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex');
const expected = `sha256=${expectedSignature}`;
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
next();
};
};
// Rota do webhook
app.post('/webhook/whatsapp',
express.raw({ type: 'application/json' }),
validateSignature('seu_webhook_secret'),
async (req, res) => {
try {
const event = JSON.parse(req.body);
console.log(`Evento ${event.event} recebido para instância ${event.instanceId}`);
switch (event.event) {
case 'messages.upsert':
await handleNewMessage(event.data);
break;
case 'messages.update':
await handleMessageUpdate(event.data);
break;
case 'connection.update':
await handleConnectionUpdate(event.data);
break;
default:
console.log('Evento não tratado:', event.event);
}
// Importante: sempre retornar 200 OK rapidamente
res.status(200).send('OK');
} catch (error) {
console.error('Erro ao processar webhook:', error);
res.status(500).send('Internal error');
}
}
);
async function handleNewMessage(data) {
console.log('Nova mensagem:', data);
// Não processar mensagens enviadas por nós mesmos
if (data.fromMe) return;
// Processar diferentes tipos de mensagem
switch (data.type) {
case 'text':
// Responder automaticamente
await sendReply(data.from, `Recebemos sua mensagem: "${data.text}"`);
break;
case 'image':
await sendReply(data.from, 'Imagem recebida! 📸');
break;
case 'location':
await sendReply(data.from, `Localização recebida: ${data.location.name}`);
break;
}
}
async function handleMessageUpdate(data) {
console.log(`Mensagem ${data.messageId} atualizada:`, data.status);
// Registrar métricas, atualizar banco de dados, etc
}
async function handleConnectionUpdate(data) {
console.log('Status de conexão:', data.status);
if (data.status === 'disconnected') {
// Notificar administradores
console.error('Instância desconectada!');
}
}
async function sendReply(to, text) {
// Implementar envio de resposta
console.log(`Enviando resposta para ${to}: ${text}`);
}
app.listen(3000, () => {
console.log('Servidor webhook rodando na porta 3000');
});Boas Práticas
1. Responda Rapidamente
Retorne 200 OK o mais rápido possível (< 5 segundos). Para processamentos demorados, use filas:
app.post('/webhook', async (req, res) => {
// Adicionar à fila para processamento assíncrono
await queue.add('process-webhook', req.body);
// Responder imediatamente
res.status(200).send('OK');
});2. Idempotência
Webhooks podem ser reenviados. Use o messageId para evitar processamento duplicado:
const processedMessages = new Set();
async function handleNewMessage(data) {
if (processedMessages.has(data.messageId)) {
console.log('Mensagem já processada:', data.messageId);
return;
}
processedMessages.add(data.messageId);
// Processar mensagem...
}3. Retry e Recuperação
O ZapFlare tenta reenviar webhooks que falharam:
- 1ª tentativa: Imediata
- 2ª tentativa: Após 1 minuto
- 3ª tentativa: Após 5 minutos
- 4ª tentativa: Após 30 minutos
4. Logs e Monitoramento
Sempre registre eventos recebidos para debugging:
app.post('/webhook', (req, res) => {
const event = req.body;
// Log estruturado
logger.info('Webhook recebido', {
event: event.event,
instanceId: event.instanceId,
timestamp: event.timestamp,
messageId: event.data?.messageId
});
// Métricas
metrics.increment('webhooks.received', {
event: event.event,
instance: event.instanceId
});
});5. Segurança
- HTTPS obrigatório: Use sempre URLs com HTTPS
- Valide assinaturas: Sempre verifique a assinatura HMAC
- IP allowlist: Considere restringir IPs (lista disponível na documentação)
- Rate limiting: Implemente limites para evitar abuse
Testando Webhooks
Teste Manual
POST /v1/whatsapp/instances/{instanceId}/webhook/testFerramentas de Desenvolvimento
Para desenvolvimento local, use tuneis seguros:
ngrok
ngrok http 3000localtunnel
lt --port 3000Configurar webhook com URL do túnel
{
"url": "https://abc123.ngrok.io/webhook",
"events": ["messages.upsert"]
}Troubleshooting
Webhook não está sendo chamado
- Verifique se a URL está acessível publicamente
- Confirme que está usando HTTPS
- Verifique os eventos configurados
- Teste manualmente com o endpoint de teste
Assinatura inválida
- Verifique se está usando o secret correto
- Certifique-se de validar o body raw (não parsed)
- Confirme o encoding (UTF-8)
Timeouts frequentes
- Otimize o processamento para < 5 segundos
- Use processamento assíncrono
- Verifique a performance do servidor