Webhook é a forma mais eficiente de receber mensagens WhatsApp em tempo real. Em vez de ficar fazendo polling na API a cada poucos segundos — desperdiçando recursos e adicionando latência — o webhook entrega cada mensagem no momento exato em que chega, direto no seu servidor.
Neste guia você aprende a configurar webhooks com a ZAP API, processar eventos em Node.js, Python e PHP, e implementar boas práticas de produção como fila de retentativa e idempotência.
O que é um webhook WhatsApp?
Um webhook é uma URL do seu servidor que a ZAP API chama automaticamente toda vez que um evento acontece na sua instância WhatsApp. Os eventos incluem:
- message.received — nova mensagem recebida
- message.sent — mensagem enviada com sucesso
- message.failed — falha na entrega
- instance.connected — instância conectou ao WhatsApp
- instance.disconnected — instância desconectou
Passo 1: Configurar o webhook na ZAP API
Com sua instância criada, configure a URL do webhook com uma chamada PUT:
curl -X PUT https://zap-api.tech/v1/instances/SEU_INSTANCE_ID/webhook \
-H "Authorization: Bearer SEU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://seu-servidor.com/webhook/whatsapp",
"events": ["message.received", "message.sent", "message.failed"]
}'
A URL precisa ser publicamente acessível (HTTPS em produção). Para desenvolvimento local, use ngrok ou localtunnel:
npx localtunnel --port 3000
# Saída: https://abc123.loca.lt — use essa URL no webhook
Passo 2: Receber eventos em Node.js
Crie um endpoint Express que processa os eventos recebidos:
import express from 'express';
const app = express();
app.use(express.json());
// Payload que a ZAP API envia
interface ZapWebhookPayload {
event: string;
instanceId: string;
data: {
messageId: string;
phone: string; // ex: "5511999998888"
name: string;
body: string; // texto da mensagem
type: string; // text | image | audio | video | document
timestamp: number;
mediaUrl?: string; // URL da mídia (se houver)
};
}
app.post('/webhook/whatsapp', async (req, res) => {
// Responda 200 imediatamente — não bloqueie o webhook
res.sendStatus(200);
const payload = req.body as ZapWebhookPayload;
if (payload.event === 'message.received') {
const { phone, name, body, type } = payload.data;
console.log(`📩 Nova mensagem de ${name} (${phone}): ${body}`);
// Processe de forma assíncrona
await processIncomingMessage(payload.data);
}
});
async function processIncomingMessage(data: ZapWebhookPayload['data']) {
// Sua lógica aqui: salvar no banco, acionar chatbot, notificar equipe, etc.
console.log(`Processando mensagem ${data.messageId} de ${data.phone}`);
}
app.listen(3000, () => console.log('Webhook server rodando na porta 3000'));
Passo 3: Receber eventos em Python
from flask import Flask, request, jsonify
import threading
app = Flask(__name__)
@app.route('/webhook/whatsapp', methods=['POST'])
def webhook():
# Responda 200 imediatamente
payload = request.json
threading.Thread(target=process_event, args=(payload,)).start()
return jsonify({'ok': True}), 200
def process_event(payload):
event = payload.get('event')
data = payload.get('data', {})
if event == 'message.received':
phone = data.get('phone')
body = data.get('body')
print(f'📩 Mensagem de {phone}: {body}')
# Sua lógica aqui
if __name__ == '__main__':
app.run(port=3000)
Passo 4: Receber eventos em PHP
<?php
// webhook.php
$payload = json_decode(file_get_contents('php://input'), true);
$event = $payload['event'] ?? '';
$data = $payload['data'] ?? [];
// Responda 200 imediatamente
http_response_code(200);
header('Content-Type: application/json');
echo json_encode(['ok' => true]);
// Processe em background (fastcgi_finish_request libera a conexão)
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
}
if ($event === 'message.received') {
$phone = $data['phone'] ?? '';
$body = $data['body'] ?? '';
error_log("Mensagem de $phone: $body");
// Sua lógica aqui
}
Boas práticas de produção
1. Responda 200 imediatamente
A ZAP API aguarda confirmação do seu servidor. Se demorar mais de 5 segundos, tentará reenviar. Sempre responda 200 antes de processar e processe de forma assíncrona (fila, thread, processo separado).
2. Implemente idempotência
Use o messageId como chave de deduplicação no banco de dados. Webhooks podem ser entregues mais de uma vez em caso de falha na rede:
// Antes de processar, verifique se já processou
const exists = await db.processedMessages.findOne({ messageId: data.messageId });
if (exists) return; // Já processado — ignorar
await db.processedMessages.create({ messageId: data.messageId });
// ... processar mensagem
3. Use fila para workloads pesados
Para chatbots com IA, integrações com CRM ou envio de mídia, use uma fila (Bull, BullMQ, Redis Streams) para não bloquear o webhook:
import { Queue } from 'bullmq';
const messageQueue = new Queue('whatsapp-messages', { connection: redis });
app.post('/webhook/whatsapp', async (req, res) => {
res.sendStatus(200); // Responde imediatamente
await messageQueue.add('process', req.body, { attempts: 3, backoff: 1000 });
});
4. Valide a origem do webhook
Configure um secret no cabeçalho e valide-o para garantir que a requisição veio da ZAP API:
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
app.post('/webhook/whatsapp', (req, res) => {
const secret = req.headers['x-webhook-secret'];
if (secret !== WEBHOOK_SECRET) return res.sendStatus(401);
// ... processar
});
Testando o webhook localmente
Use o ngrok para expor seu servidor local:
# Instalar ngrok: https://ngrok.com
ngrok http 3000
# Saída:
# Forwarding https://abc123.ngrok.io -> http://localhost:3000
# Configure o webhook com a URL do ngrok:
curl -X PUT https://zap-api.tech/v1/instances/SEU_INSTANCE/webhook \
-H "Authorization: Bearer SEU_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://abc123.ngrok.io/webhook/whatsapp"}'
Estrutura do payload completo
{
"event": "message.received",
"instanceId": "inst_abc123",
"data": {
"messageId": "msg_xyz789",
"phone": "5511999998888",
"name": "João Silva",
"body": "Olá, preciso de ajuda",
"type": "text",
"timestamp": 1709654400,
"pushName": "João",
"isGroup": false,
"mediaUrl": null
}
}
Para mensagens de imagem, áudio ou vídeo, o campo mediaUrl conterá a URL temporária da mídia (válida por 24h).
FAQ
Preciso de HTTPS para o webhook?
Em produção sim — a ZAP API só envia para URLs HTTPS. Em desenvolvimento, use ngrok ou localtunnel que já fornecem HTTPS gratuitamente.
O que acontece se meu servidor estiver offline?
A ZAP API faz até 3 tentativas de reenvio com backoff exponencial (1min, 5min, 15min). Mensagens não entregues ficam na fila por 24h.
Posso ter múltiplos webhooks?
Cada instância aceita uma URL de webhook. Para fanout (enviar para múltiplos endpoints), implemente esse roteamento no seu próprio servidor.
Como debugar webhooks?
Use o webhook.site para inspecionar payloads antes de implementar seu servidor, ou ative logs detalhados no ngrok com ngrok http 3000 --inspect.