ZynzapAPI

Webhooks

Receba mensagens, status de entrega, conexão dos números e mais no seu endpoint, com assinatura HMAC, novas tentativas e proteção contra SSRF.

Com webhooks, o Zynzap faz um POST com JSON no seu endpoint a cada evento escolhido. É a forma mais simples de reagir a mensagens recebidas: não precisa manter conexão aberta. Os eventos são os mesmos do stream /events.

Configurar

Webhooks pertencem a uma chave de API. No painel, abra API e integrações, clique na chave e use a seção Webhooks:

  • Até 5 endereços por chave, cada um com os seus eventos.
  • Só aparecem os eventos que a chave já poderia ler em /events: exige events:read, respeita os números da chave, e eventos da conta inteira (campanhas e aquecimento) só valem para chaves sem restrição de números. Se a chave for revogada, vencer ou perder a permissão, as entregas param na hora.
  • Segredo de assinatura (whsec_...): aparece uma única vez ao criar. Novo segredo troca o segredo com carência opcional de 24 horas (as entregas levam as duas assinaturas até o fim dela).
  • Enviar teste manda um evento webhook.ping (nenhuma mensagem sai pelo WhatsApp). O Registro mostra status HTTP, tempo, tentativas e motivo das falhas das últimas entregas.
  • Ativar/desativar a qualquer momento. Ao reativar, a entrega recomeça daquele momento em diante.

Requisitos do endereço

  • https:// público, porta 443 ou 8443, sem usuário e senha na URL.
  • Endereços locais, de rede privada, loopback, link-local, CGNAT ou de metadados de nuvem são recusados no cadastro e em cada entrega: o domínio é resolvido de novo e fixado no endereço conferido.
  • Redirecionamentos não são seguidos (um 3xx conta como falha). Não use proxy de redirecionamento.

Para desenvolver localmente, exponha sua máquina com um túnel HTTPS (por exemplo, cloudflared ou ngrok).

O que chega

POST /zynzap HTTP/1.1
Content-Type: application/json
User-Agent: Zynzap-Webhooks/1
X-Zynzap-Event-Id: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f
X-Zynzap-Event-Type: MessageReceived
X-Zynzap-Delivery-Id: 0b9e8d7c-6b5a-4938-8271-6a5b4c3d2e1f
X-Zynzap-Attempt: 1
X-Zynzap-Timestamp: 1760280000
X-Zynzap-Signature: v1=5d41402abc4b2a76b9719d911017c592...

{"id":"6f1c2d3e-...","type":"MessageReceived","created_at":"2026-10-12T14:00:00.000Z","cursor":"1042","session_id":"8c1f7a52-...","webhook_id":"4b3a2918-...","data":{"MessageReceived":{...}}}
CabeçalhoSignificado
X-Zynzap-Event-IdMesmo valor de id. Use para ignorar repetições.
X-Zynzap-Event-TypeTipo do evento (ou webhook.ping).
X-Zynzap-Delivery-IdIdentificador desta entrega (aparece no Registro do painel).
X-Zynzap-AttemptNúmero da tentativa, a partir de 1.
X-Zynzap-TimestampMomento da assinatura, em segundos Unix.
X-Zynzap-Signaturev1=<hex>; durante a carência de rotação, v1=...,v1=....
Campo do corpoSignificado
idIdentificador único do evento (idempotência).
typeTipo do evento.
created_atQuando o evento aconteceu (ISO 8601, UTC).
cursorPosição no histórico. Ordene por ele e use em /events?after_cursor= ou ReplayEvents para buscar o que faltou.
session_idNúmero de origem (null em eventos da conta inteira).
webhook_idEndereço que recebeu a entrega.
data{ "<Tipo>": { ...campos } }, o mesmo JSON dos eventos domain de /events.

Verificar a assinatura

A assinatura é HMAC-SHA256(segredo, "{X-Zynzap-Timestamp}.{corpo bruto}") em hexadecimal, com o prefixo v1=. Regras:

  1. Calcule sobre o corpo bruto, antes de interpretar o JSON (reformatar o JSON muda a assinatura).
  2. Compare em tempo constante.
  3. Recuse entregas com mais de 5 minutos (proteção contra reenvio).
  4. Aceite se qualquer v1= conferir (carência de rotação).
import crypto from "node:crypto";
import http from "node:http";

// rawBody: corpo BRUTO (string), antes de JSON.parse.
export function verifyZynzap(rawBody, headers, secret = process.env.ZYNZAP_WEBHOOK_SECRET) {
  const timestamp = headers["x-zynzap-timestamp"] ?? "";
  if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
  return (headers["x-zynzap-signature"] ?? "").split(",").some((part) => {
    const [scheme, value = ""] = part.trim().split("=");
    return (
      scheme === "v1" &&
      value.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(value), Buffer.from(expected))
    );
  });
}

http
  .createServer((req, res) => {
    let rawBody = "";
    req.setEncoding("utf8");
    req.on("data", (chunk) => (rawBody += chunk));
    req.on("end", () => {
      if (!verifyZynzap(rawBody, req.headers)) {
        res.writeHead(401).end();
        return;
      }
      // Responda rápido e processe numa fila.
      res.writeHead(204).end();
      const event = JSON.parse(rawBody);
      console.log(event.type, event.id);
    });
  })
  .listen(8080);

Guarde o segredo como uma senha (variável de ambiente ou cofre). Se ele vazar, gere um Novo segredo. Se só o endereço vazar, a assinatura impede que terceiros se passem pelo Zynzap.

Responder, novas tentativas e desativação

  • Responda com qualquer 2xx em até 10 segundos (5 s para conectar) e processe depois, numa fila. O corpo da resposta é ignorado (lido até 16 KiB).
  • Respostas fora de 2xx, redirecionamentos, tempo esgotado ou falha de conexão contam como falha. O Zynzap tenta de novo após 30 s, 2 min, 10 min, 30 min, 1 h, 2 h e 4 h: 8 tentativas no total, cerca de 8 horas.
  • Depois de 15 falhas seguidas (em qualquer entrega), o endereço é desativado, a linha do tempo da chave registra o motivo e ele aparece como Desativado por falhas. Corrija e reative no painel; a entrega recomeça a partir daquele momento (use cursor e ReplayEvents para buscar o que perdeu).
  • A entrega é pelo menos uma vez: o mesmo id pode chegar mais de uma vez e a ordem não é garantida. Deduplique por id e ordene por cursor.

Testar

  1. Cadastre o endereço e clique em Enviar teste: chega um webhook.ping assinado.
  2. Confira o Registro (status, tempo, motivo da falha).
  3. Para testar sem o painel, gere a assinatura você mesmo (aba cURL acima).
  4. Para eventos reais, envie uma mensagem de outro celular para o seu número: chega MessageReceived.
webhook.ping
{
  "id": "6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
  "type": "webhook.ping",
  "created_at": "2026-10-12T14:00:00.000Z",
  "cursor": null,
  "session_id": null,
  "webhook_id": "4b3a2918-0f1e-4d2c-9b8a-7c6d5e4f3a2b",
  "data": { "message": "Evento de teste do Zynzap. Nenhuma mensagem foi enviada." }
}

Tipos de evento

Lista gerada do catálogo do Zynzap, com um exemplo de corpo para cada tipo (valores fictícios).

Mensagens

Números

Grupos e presença

Atendimento e chamadas

Campanhas e aquecimento

Webhook ou stream?

Os dois entregam os mesmos eventos. O webhook é melhor para sistemas que só recebem requisições (n8n, CRMs, funções serverless). O stream /events é bom para um processo que já fica conectado e quer latência mínima. Dá para usar os dois com a mesma chave.

O Zynzap é um produto independente e não é afiliado, associado, autorizado, endossado nem oficialmente ligado ao WhatsApp, à Meta Platforms, Inc. ou a qualquer empresa do grupo. WhatsApp é marca registrada da Meta Platforms, Inc.