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 secret usado na assinatura; POST /webhooks/{webhookId}/rotate-secret gera outro.
  • POST /webhooks/{webhookId}/test envia 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

EventoQuando
messageMensagem recebida
message.anyQualquer mensagem: recebida ou enviada pela conta (fromMe: true)
message.ackStatus de uma mensagem enviada: sent, delivered, read, played ou failed
message.reactionReação adicionada ou removida (emoji vazio)
message.revokedMensagem apagada para todos
message.editedTexto de uma mensagem editado
session.statusConexão mudou: WORKING, SCAN_QR_CODE, STOPPED, FAILED…
queue.sent, queue.failedResultado 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 2xx em 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 id do 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 por PATCH /webhooks/{webhookId}.
  • Histórico: GET /webhooks/{webhookId}/deliveries lista as entregas; .../retry reenvia 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.