É 2h da manhã. Cliente liga: "as confirmações de pedido pararam de chegar no WhatsApp dos meus clientes". Você abre o terminal, faz tail -f nos logs do servidor, vê 200 milhões de linhas, procura "webhook" no meio do barulho, encontra alguns 500 misturados com 200, mas qual deu errado? Para qual cliente? Foi nosso bug ou bug do servidor dele?
Esse cenário é a razão de existir o console de webhooks da ZAP API. Toda entrega ficou registrada com payload completo, headers, status HTTP, latência, e — se falhou — você consegue reenviar com 1 clique ou 1 chamada de API. Sem tail, sem grep, sem SSH.
O que o console registra
Cada vez que a ZAP API tenta entregar um webhook ao seu endpoint, fica registrado:
- ID único da entrega (delivery_id, ex.:
dlv_a1b2c3) - URL de destino que você cadastrou
- Tipo do evento (message.received, message.status, instance.connected, etc.)
- Payload completo JSON enviado
- Headers incluindo o HMAC
X-Zap-Signature - Status HTTP da resposta do seu servidor (200, 500, timeout, etc.)
- Latência (quanto demorou seu endpoint a responder)
- Tentativa (1, 2, 3, 4, 5 — após 5 tentativas vai para DLQ)
- Próxima tentativa (timestamp do retry com backoff exponencial)
- Resposta truncada do seu endpoint (primeiros 500 caracteres)
Listando entregas via API
curl -X GET "https://api.zap-api.tech/v1/webhooks/deliveries?status=failed&from=2026-05-15T00:00:00Z" \
-H "Authorization: Bearer tk_seu_token"
Resposta:
{
"deliveries": [
{
"id": "dlv_a1b2c3",
"url": "https://meusite.com.br/webhooks/zap",
"event": "message.received",
"instanceId": "inst_xyz789",
"status": "failed",
"httpStatus": 500,
"attempt": 3,
"nextRetryAt": "2026-05-15T03:42:00Z",
"latencyMs": 28456,
"createdAt": "2026-05-15T03:30:11Z"
}
],
"pagination": { "total": 47, "page": 1, "pageSize": 50 }
}
Filtros disponíveis
status— pending, succeeded, failed, dlqevent— tipo do eventoinstanceId— só de uma instânciafrom/to— janela de tempo (ISO 8601)httpStatusGte— status HTTP >= valor (ex.: 500 para pegar só erros do servidor)page/pageSize— paginação
Inspecionando uma entrega específica
curl -X GET https://api.zap-api.tech/v1/webhooks/deliveries/dlv_a1b2c3 \
-H "Authorization: Bearer tk_seu_token"
Resposta inclui campos extras que a listagem omite (payload completo, headers, response body):
{
"id": "dlv_a1b2c3",
"url": "https://meusite.com.br/webhooks/zap",
"event": "message.received",
"payload": {
"type": "message.received",
"instanceId": "inst_xyz789",
"message": { "from": "5511999998888", "body": { "text": "Oi" } }
},
"headers": {
"X-Zap-Signature": "sha256=a3b4c5...",
"X-Zap-Delivery": "dlv_a1b2c3",
"X-Zap-Event": "message.received"
},
"responseBody": "Internal Server Error\n at handler (...)",
"httpStatus": 500,
"latencyMs": 28456,
"attempts": [
{ "n": 1, "at": "2026-05-15T03:30:11Z", "httpStatus": 500 },
{ "n": 2, "at": "2026-05-15T03:32:15Z", "httpStatus": 500 },
{ "n": 3, "at": "2026-05-15T03:36:20Z", "httpStatus": 500 }
]
}
Note o responseBody com o stack trace truncado. Já te dá pista do bug sem você precisar abrir log do servidor.
Replay manual de uma entrega
Após corrigir o bug no seu servidor, você quer reentregar os webhooks que falharam (caso contrário, você perdeu eventos). Use o endpoint replay:
curl -X POST https://api.zap-api.tech/v1/webhooks/deliveries/dlv_a1b2c3/replay \
-H "Authorization: Bearer tk_seu_token"
Isso cria uma nova entrega com mesmo payload e mesmo HMAC, com novo delivery_id. A entrega original fica como histórico.
Script de replay em massa
Cenário comum: você teve 30 minutos de downtime no seu servidor. Quer reenviar todos os webhooks que falharam nessa janela.
// replay-failed.ts
import axios from "axios";
const ZAP = axios.create({
baseURL: "https://api.zap-api.tech/v1",
headers: { Authorization: `Bearer ${process.env.ZAP_TOKEN}` },
});
async function replayFailedWindow(fromIso: string, toIso: string) {
let page = 1;
let total = 0;
while (true) {
const { data } = await ZAP.get("/webhooks/deliveries", {
params: {
status: "failed",
from: fromIso,
to: toIso,
page,
pageSize: 100,
},
});
if (data.deliveries.length === 0) break;
for (const d of data.deliveries) {
try {
await ZAP.post(`/webhooks/deliveries/${d.id}/replay`);
total++;
console.log(`✅ replay ${d.id} (${d.event})`);
} catch (err) {
console.error(`❌ erro ${d.id}:`, err.message);
}
await new Promise(r => setTimeout(r, 200)); // throttle
}
if (data.deliveries.length < 100) break;
page++;
}
console.log(`Total replayed: ${total}`);
}
// Janela do downtime
replayFailedWindow("2026-05-15T03:00:00Z", "2026-05-15T03:30:00Z");
DLQ (Dead Letter Queue)
Após 5 tentativas com backoff exponencial (1min, 5min, 30min, 2h, 6h), a entrega vai para DLQ. Não é tentada de novo automaticamente. Você pode listar com:
curl -X GET "https://api.zap-api.tech/v1/webhooks/deliveries?status=dlq" \
-H "Authorization: Bearer tk_seu_token"
E reentregar manualmente com replay quando o problema for resolvido. DLQ é mantida por 30 dias.
Validação HMAC no seu endpoint
Toda entrega tem o header X-Zap-Signature: sha256=. Valide-o para garantir que a requisição realmente veio da ZAP API.
import crypto from "crypto";
import express from "express";
const app = express();
const SECRET = process.env.ZAP_WEBHOOK_SECRET!; // copie do painel
// IMPORTANTE: capturar raw body antes do JSON parser
app.post("/webhooks/zap", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-zap-signature"] as string;
if (!signature) return res.status(401).send("missing signature");
const expected = "sha256=" + crypto
.createHmac("sha256", SECRET)
.update(req.body)
.digest("hex");
// Comparação timing-safe (resiste a timing attacks)
const sigBuf = Buffer.from(signature);
const expBuf = Buffer.from(expected);
if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) {
return res.status(401).send("invalid signature");
}
const event = JSON.parse(req.body.toString());
// ... processa o evento
res.status(200).send("ok");
});
Casos práticos
Caso 1: Bug que só apareceu em produção
Cliente reporta que algumas confirmações de pedido não chegam. No console, você filtra por event=message.status, vê 12 entregas em status DLQ com httpStatus=500, abre uma e descobre stack trace de Null Pointer Exception em campo opcional. Corrige, faz replay em massa das 12 e o cliente nunca soube que existiu o problema.
Caso 2: Mudança de URL do webhook
Time muda backend de Heroku para Railway. URL do endpoint muda. Antes da migração, você lista entregas pendentes (status=pending). Após o deploy do novo backend, atualiza URL no painel ZAP, faz replay das pendentes — todas chegam na URL nova.
Caso 3: Auditoria fiscal
Receita pede para o cliente provar que enviou notificação de cobrança PIX a determinado consumidor em data específica. Cliente filtra event=message.sent + intervalo de tempo + número, exporta o JSON da entrega como evidência (com X-Zap-Signature garantindo integridade).
FAQ
Quanto tempo as entregas ficam guardadas?
Entregas com status succeeded: 7 dias. Failed/DLQ: 30 dias. Após esse prazo, são purgadas automaticamente. Para retenção mais longa, exporte regularmente via API e arquive em S3 próprio.
Replay duplica o evento?
Sim — chega no seu endpoint como uma nova requisição com novo delivery_id. Por isso, sempre desenhe seu webhook handler para ser idempotente: detecte se já processou aquele evento (ex.: pelo messageId no payload) e ignore duplicatas.
Posso adicionar headers customizados na entrega?
Sim. No painel, em Configurações > Webhooks, adicione headers como X-API-Key: meu_token_interno. Eles vão em toda entrega original. Replay também repete os headers customizados.
Posso restringir IP que envia webhook?
Sim — todos os webhooks da ZAP API saem de IPs fixos (lista pública na documentação). Configure firewall do seu servidor para aceitar só esses IPs no path do webhook. Soma com a validação HMAC para defesa em profundidade.
Diferença entre 5xx e timeout?
5xx é resposta HTTP do seu servidor (ele respondeu, mas com erro). Timeout é seu endpoint não respondeu dentro de 30s. Ambos contam como falha e disparam retry, mas timeout pode indicar problema de rede ou endpoint travado — investigue diferente. No console, timeout aparece como httpStatus: 0.
Próximo passo
Acesse o console em /dashboard/webhooks. Criar conta grátis e teste a integração com replay desde o primeiro dia.