Webhooks
Receba os eventos da instância no seu sistema, confira a assinatura e trate repetições.
Um webhook é um endereço do seu sistema que recebe, por POST em JSON, os eventos da instância: mensagens
recebidas e enviadas, confirmações de entrega e leitura, mudanças de conexão e muito mais.
Cadastrar
Pelo painel (aba Webhooks da instância) ou pela API:
curl -X POST "https://app.onzap.io/v1/instances/SUA_INSTANCIA/webhooks" \
-H "X-Instance-Token: SEU_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://seu-sistema.com.br/webhooks/onzap", "events": ["message.any", "message.ack"]}'- Até 5 webhooks por instância, cada um com os seus eventos (
"*"assina todos). - A resposta traz o
secretusado na assinatura;POST /webhooks/{webhookId}/rotate-secretgera outro. POST /webhooks/{webhookId}/testenvia um evento de teste para conferir o endereço.
Formato
Todo evento chega com o mesmo envelope, nos dois tipos de instância:
{
"id": "evt_01jabcdefghjkmnpqrstvwxyz0",
"event": "message.any",
"instanceId": "i01jabcdefghjkmnpqrstvwxyz",
"provider": "onzap",
"timestamp": 1767225600000,
"payload": {
"id": "false_5511999999999@c.us_3EB0C0A1B2C3D4E5F6",
"chatId": "5511999999999@c.us",
"isGroup": false,
"chatName": "Maria",
"chatPicture": "https://app.onzap.io/v1/avatars/i01jabcdefghjkmnpqrstvwxyz/5511999999999@c.us?exp=…&sig=…",
"fromMe": false,
"from": { "id": "5511999999999@c.us", "phone": "5511999999999", "pushName": "Maria" },
"type": "text",
"text": "Oi! Vocês entregam em Campinas?"
},
"me": { "id": "5511888888888@c.us", "pushName": "Minha Loja" }
}Os headers trazem o tipo (X-OnZap-Event), o id do evento (X-OnZap-Event-Id), o id da entrega
(X-OnZap-Delivery) e a assinatura (X-OnZap-Signature). Todos os eventos e os seus formatos estão em
Eventos de webhook.
Eventos principais
| Evento | Quando |
|---|---|
message | Mensagem recebida |
message.any | Qualquer mensagem: recebida ou enviada pela conta (fromMe: true) |
message.ack | Status de uma mensagem enviada: sent, delivered, read, played ou failed |
message.reaction | Reação adicionada ou removida (emoji vazio) |
message.revoked | Mensagem apagada para todos |
message.edited | Texto de uma mensagem editado |
session.status | Conexão mudou: WORKING, SCAN_QR_CODE, STOPPED, FAILED… |
queue.sent, queue.failed | Resultado de um envio da fila |
message ou message.any? message traz só as recebidas. Para acompanhar a conversa inteira, inclusive o que
foi enviado pelo celular ou por outro sistema, assine message.any. Nas enviadas, source diz a origem: api ou
app (celular).
Assinatura
O header X-OnZap-Signature: t=<unix>,v1=<hex> traz o HMAC-SHA256 de "<t>.<corpo cru>" com o secret do webhook.
Confira antes de processar e recuse t com mais de 5 minutos:
// Node.js
import crypto from "node:crypto";
function assinaturaValida(corpoCru, header, segredo) {
const partes = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const esperado = crypto.createHmac("sha256", segredo).update(`${partes.t}.${corpoCru}`).digest("hex");
const recente = Math.abs(Date.now() / 1000 - Number(partes.t)) < 300;
return recente && crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(partes.v1 ?? ""));
}// PHP
function assinaturaValida(string $corpoCru, string $header, string $segredo): bool {
parse_str(str_replace(',', '&', $header), $p);
$esperado = hash_hmac('sha256', $p['t'] . '.' . $corpoCru, $segredo);
return abs(time() - (int) $p['t']) < 300 && hash_equals($esperado, $p['v1'] ?? '');
}# Python
import hashlib, hmac, time
def assinatura_valida(corpo_cru: bytes, header: str, segredo: str) -> bool:
partes = dict(p.split("=", 1) for p in header.split(","))
esperado = hmac.new(segredo.encode(), f"{partes['t']}.".encode() + corpo_cru, hashlib.sha256).hexdigest()
recente = abs(time.time() - int(partes["t"])) < 300
return recente and hmac.compare_digest(esperado, partes.get("v1", ""))Use o corpo cru, exatamente como chegou: se o seu framework já converteu o JSON, a assinatura não bate.
Entrega
- Confirmação: responda qualquer
2xxem até 10 segundos. Processe depois, numa fila sua, se for demorado. - Novas tentativas: sem
2xx, tentamos de novo após 10 s, 1 min, 5 min, 15 min, 1 h, 3 h, 6 h e 12 h. - Pelo menos uma vez: o mesmo evento pode chegar mais de uma vez e fora de ordem. Use o
iddo envelope para descartar repetidos. - Desativação: um endereço que falha sem parar por 24 horas (ou responde
410) é desativado, e avisamos por e-mail. Reative pelo painel ou porPATCH /webhooks/{webhookId}. - Histórico:
GET /webhooks/{webhookId}/deliverieslista as entregas;.../retryreenvia uma.
Inspetor
Na aba Inspetor da instância, um clique cria um webhook apontando para um link da própria OnZap. Os eventos aparecem na hora, montados como conversa (balões, confirmações, reações e mídia) ou como requisição crua, com headers e a conferência da assinatura. É o jeito mais rápido de ver o que chega antes de programar o seu endpoint.