Skip to content

Webhooks

Webhooks permitem que sua aplicação receba notificações em tempo real sobre eventos que ocorrem no WhatsApp.

Como Funcionam

  1. Você configura uma URL no seu servidor para receber eventos
  2. Quando um evento ocorre (nova mensagem, status, etc), o ZapFlare envia uma requisição POST para sua URL
  3. Sua aplicação processa o evento e responde com status 200 OK

Configurando Webhooks

Durante a Criação da Instância

json
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

json
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.

json
{
  "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).

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "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

javascript
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');
});
python
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
<?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

javascript
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:

javascript
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:

javascript
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:

javascript
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

bash
POST /v1/whatsapp/instances/{instanceId}/webhook/test

Ferramentas de Desenvolvimento

Para desenvolvimento local, use tuneis seguros:

ngrok

bash
ngrok http 3000

localtunnel

bash
lt --port 3000

Configurar webhook com URL do túnel

json
{
  "url": "https://abc123.ngrok.io/webhook",
  "events": ["messages.upsert"]
}

Troubleshooting

Webhook não está sendo chamado

  1. Verifique se a URL está acessível publicamente
  2. Confirme que está usando HTTPS
  3. Verifique os eventos configurados
  4. Teste manualmente com o endpoint de teste

Assinatura inválida

  1. Verifique se está usando o secret correto
  2. Certifique-se de validar o body raw (não parsed)
  3. Confirme o encoding (UTF-8)

Timeouts frequentes

  1. Otimize o processamento para < 5 segundos
  2. Use processamento assíncrono
  3. Verifique a performance do servidor

Próximos Passos

Desenvolvido com ❤️ pela equipe ZapFlare