Webhook é a espinha dorsal de qualquer integração WhatsApp séria — é por ele que sua aplicação recebe mensagens em tempo real, status de entrega, cliques em botão e mudanças de presença. Mas a maioria das APIs trata webhook como "POST e esquece": se cair, você perde o evento; se um atacante descobrir a URL, ele pode forjar mensagens; se sua API ficar 30s lenta, o provider desiste.
Na ZAP API a plataforma de webhooks foi reconstruída em 2026 com 3 garantias que diferenciam um webhook "amador" de um webhook de produção: assinatura HMAC-SHA256, retry com backoff exponencial e Dead Letter Queue (DLQ) com replay one-click.
Por que isso importa
Um webhook sem essas três proteções é uma mina terrestre técnica:
- Sem assinatura: qualquer pessoa que descobrir a URL pode disparar eventos forjados — cobranças falsas, mensagens fantasmas, scripts maliciosos.
- Sem retry: se sua aplicação cair por 5 minutos durante um deploy, você perde todas as mensagens recebidas naquela janela. Para sempre.
- Sem DLQ: quando um payload específico falhar (ex: bug no seu parser), você não tem como reprocessar depois de corrigir o código.
1. Assinatura HMAC-SHA256 — provando que veio da ZAP API
Toda chamada de webhook envia o header X-ZapApi-Signature com o HMAC-SHA256 do corpo bruto, calculado com o seu webhook secret (gerado quando você cadastra a URL no painel).
// Node.js / Express
const crypto = require('crypto');
const express = require('express');
const app = express();
const WEBHOOK_SECRET = process.env.ZAPAPI_WEBHOOK_SECRET;
// IMPORTANTE: use raw body para o HMAC bater
app.post(
'/webhook/zap',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-zapapi-signature'];
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
// timingSafeEqual evita timing attack
const ok = crypto.timingSafeEqual(
Buffer.from(signature || '', 'hex'),
Buffer.from(expected, 'hex')
);
if (!ok) {
return res.status(401).json({ error: 'invalid_signature' });
}
const event = JSON.parse(req.body.toString());
console.log('Evento autenticado:', event.type);
// Responda 2xx em até 5s — qualquer outra coisa entra em retry
res.status(200).json({ ok: true });
}
);
app.listen(3000);
Regra de ouro: nunca confie em payload de webhook sem validar a assinatura. Mesmo que a URL pareça "secreta", basta um log mal redactado, um header vazado em screenshot, ou um proxy mal configurado para que a URL seja descoberta.
2. Retry com exponential backoff + jitter
Quando sua aplicação retorna qualquer coisa que não seja 2xx (ou demora mais de 5 segundos), a ZAP API reagenda a entrega com a sequência:
- Tentativa 1: imediata
- Tentativa 2: 1s depois (+ jitter aleatório)
- Tentativa 3: 2s depois
- Tentativa 4: 4s depois
- Tentativa 5: 8s depois
- Tentativa 6: 16s depois
O jitter (variação aleatória) evita o thundering herd — se 1.000 webhooks falharem ao mesmo tempo (típico em outage parcial), o retry não bate todos juntos no mesmo segundo.
Por que parar em 5 retries? Porque depois disso, ou o seu sistema está fora do ar há mais de 30 segundos (intervenção humana necessária), ou o payload tem algum bug específico (precisa de inspeção manual). Continuar tentando indefinidamente só queima recursos.
3. Dead Letter Queue (DLQ) — segunda chance, no seu tempo
Depois das 5 tentativas falhas, o evento vai para a DLQ — uma fila de "entregas mortas" que ficam disponíveis no painel /dashboard/webhooks por 30 dias.
De lá, com um clique no botão "Replay", você reenvia a entrega — útil quando:
- Você acabou de fazer deploy do fix e quer reprocessar a fila.
- Um cliente reclamou que "não recebeu a confirmação de pedido" — você acha o evento na DLQ, replay, problema resolvido sem precisar ressincronizar do zero.
- Quer testar localmente com um payload real de produção — replay para um endpoint de staging.
Listando a DLQ via API
// Listar entregas falhas
GET /v1/webhooks/deliveries?status=failed&limit=50
Authorization: Bearer tk_seu_token
// Resposta
{
"deliveries": [
{
"id": "del_abc123",
"instanceId": "inst_xxx",
"event": "message.received",
"url": "https://meu-app.com/webhook",
"status": "failed",
"attempts": 5,
"lastError": "ECONNREFUSED",
"payload": { /* ...corpo original... */ },
"createdAt": "2026-04-30T12:34:56Z"
}
],
"nextCursor": "..."
}
// Reprocessar uma entrega específica
POST /v1/webhooks/deliveries/del_abc123/replay
Authorization: Bearer tk_seu_token
Padrões de implementação que evitam dor de cabeça
Idempotência: cada evento traz um event_id único
Mesmo com retry, a ZAP API pode entregar o mesmo evento mais de uma vez (ex: sua aplicação respondeu 200 mas a conexão caiu antes). Salve o event.id em uma tabela e ignore duplicatas:
const seen = await db.events.findOne({ where: { eventId: event.id } });
if (seen) {
return res.status(200).json({ ok: true, duplicate: true });
}
await db.events.create({ eventId: event.id, payload: event });
// ...processar...
Acknowledge primeiro, processe depois
Não rode lógica pesada antes de retornar 200. Coloque o evento em uma fila interna (BullMQ, Redis Streams, SQS) e responda imediatamente:
app.post('/webhook/zap', async (req, res) => {
// 1. Valida HMAC (rápido)
if (!validSignature(req)) return res.status(401).end();
// 2. Enfileira (rápido)
await queue.add('process-webhook', JSON.parse(req.body));
// 3. Responde (rápido)
res.status(200).json({ ok: true });
});
Esse padrão derruba seu p99 de webhook de "1500ms quando o banco está lento" para "8ms sempre".
Eventos disponíveis
A ZAP API entrega 21 tipos de evento via webhook:
message.received,message.sent,message.status,message.read,message.reaction,message.edited,message.deletedinstance.connected,instance.disconnected,instance.qrcode,instance.testgroup.join,group.leave,group.updatecontact.update,presence.updatecall.received,call.rejectedstatus.viewed,label.update,newsletter.update
Você escolhe quais quer receber em cada instância, no painel ou via PUT /v1/instances/:id/webhook.
FAQ
- Posso ter mais de uma URL de webhook por instância?
Sim — configure URLs diferentes por categoria de evento (ex: mensagens vs status de instância) ou aponte tudo para um único endpoint que faz fan-out interno. - O HMAC é obrigatório?
O header é sempre enviado. Validar do seu lado é opcional, mas fortemente recomendado em produção. - O que acontece se eu não habilitar webhook?
Você pode usar polling (GET /v1/instances/:id/messages), mas perde latência (5-30s) e gasta mais quota de API. - Há rate limit no webhook?
Não há rate limit de saída — entregamos tudo. O limite é só na sua aplicação aguentar receber. - Quanto tempo a DLQ retém entregas?
30 dias. Depois disso são apagadas (LGPD).
Criar conta e configurar webhooks com HMAC, retry e DLQ — trial 7 dias, sem cartão.