Pular para o conteúdo principal
Webhooks são notificações HTTP que o Z2Pay envia para o seu servidor sempre que algo acontece na sua conta (uma transação é paga, um reembolso é concluído, uma assinatura é cancelada, etc.). Em vez de você ficar consultando a API repetidamente, o Z2Pay faz um POST para uma URL que você cadastra.
Esta página explica como receber e validar os eventos. Para cadastrar, listar e remover endpoints de webhook, veja Core API: Webhooks. Para a lista de eventos disponíveis, veja Eventos.

Como funciona

1

Você cadastra um endpoint

Registre uma URL pública (HTTPS) e, opcionalmente, um secret. Você também escolhe quais eventos quer receber — uma lista vazia significa “todos os eventos”. Veja Core API: Webhooks.
2

Um evento acontece

Algo muda na sua conta (ex.: uma transação é paga). O Z2Pay seleciona seus webhooks ativos que escutam aquele tipo de evento.
3

O Z2Pay faz POST no seu endpoint

Para cada webhook correspondente, o Z2Pay envia um POST com o envelope JSON do evento no corpo da requisição e cabeçalhos de identificação/assinatura.
4

Seu servidor responde 2xx

Responda com um status 2xx o mais rápido possível. Qualquer resposta fora da faixa 2xx é tratada como falha de entrega.

O envelope do evento

Todo webhook chega no mesmo formato de envelope. O conteúdo específico do recurso fica sempre dentro de data.
{
  "id": "whd_3f8a2c1d9e4b5a6c7d8e9f01",
  "type": "transaction.paid",
  "data": {
    "id": "txn_a1b2c3d4e5f6a7b8c9d0e1f2",
    "status": "paid",
    "amount": 15000,
    "paymentMethod": "credit_card"
  },
  "occurredAt": "2026-06-24T18:32:05.184Z",
  "companyId": "comp_9a8b7c6d5e4f3a2b1c0d9e8f"
}
id
string
Identificador único desta entrega (prefixo whd_). Cada tentativa de entregar o mesmo evento ao mesmo endpoint usa o mesmo id — use-o como chave de idempotência (veja abaixo).
type
string
O tipo do evento, ex.: transaction.paid. Lista completa em Eventos.
data
object
O objeto do recurso relacionado ao evento (a transação, o reembolso, a assinatura, etc.). O formato de data depende do type.
occurredAt
string
Data e hora em que o evento foi despachado, em ISO 8601 com timezone (UTC).
companyId
string
Identificador da sua company (prefixo comp_).
Valores monetários em data são sempre inteiros em centavos. 15000 significa R$ 150,00. Não assuma casas decimais. Veja Convenções.

Cabeçalhos da requisição

Todo POST de webhook inclui:
CabeçalhoSempre presenteDescrição
Content-TypeSimapplication/json
User-AgentSimZ2Pay-Webhooks/1.0
X-Webhook-Event-IdSimIdentificador do evento de origem. Usado para correlação.
X-Webhook-TimestampApenas com secretMomento da assinatura, em ISO 8601 com timezone.
X-Webhook-SignatureApenas com secretAssinatura HMAC-SHA256 do corpo, em hexadecimal.
Os cabeçalhos X-Webhook-Timestamp e X-Webhook-Signature só são enviados quando você cadastrou um secret no webhook. Sem secret, não há assinatura para validar — por isso recomendamos sempre configurar um secret.

Verificando a assinatura

Quando o webhook tem secret, o Z2Pay calcula a assinatura assim:
X-Webhook-Signature = HMAC-SHA256(secret, JSON.stringify(payload))   // em hexadecimal
Onde payload é o envelope inteiro (o JSON com id, type, data, occurredAt e companyId) — exatamente o corpo que chega na requisição.
Compute o HMAC sobre o corpo bruto (raw body) da requisição, exatamente como recebido. Se você fizer parse para objeto e depois re-serializar (JSON.stringify), a ordem das chaves ou o espaçamento pode mudar e a assinatura não vai bater. Capture o raw body antes de qualquer middleware que faça parse de JSON.

Exemplo em Node.js

import { createHmac, timingSafeEqual } from 'node:crypto';

/**
 * Valida a assinatura de um webhook do Z2Pay.
 *
 * @param {string} rawBody  Corpo bruto da requisição (string), NÃO re-serializado.
 * @param {string} signature Valor do cabeçalho 'x-webhook-signature'.
 * @param {string} secret   O secret cadastrado no webhook.
 * @returns {boolean}
 */
function verifyWebhookSignature(rawBody, signature, secret) {
  const expected = createHmac('sha256', secret).update(rawBody).digest('hex');

  // Comprimentos diferentes -> assinatura inválida (timingSafeEqual exige tamanhos iguais)
  if (expected.length !== signature.length) return false;

  return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
Exemplo de uso com Express (capturando o raw body):
import express from 'express';

const app = express();

// Captura o raw body como string ANTES de qualquer parse de JSON.
app.use('/webhooks/z2pay', express.raw({ type: 'application/json' }));

app.post('/webhooks/z2pay', (req, res) => {
  const rawBody = req.body.toString('utf8');
  const signature = req.header('x-webhook-signature');

  const ok = verifyWebhookSignature(rawBody, signature, process.env.Z2PAY_WEBHOOK_SECRET);
  if (!ok) {
    return res.status(401).send('invalid signature');
  }

  const event = JSON.parse(rawBody);

  // TODO: enfileirar/processar de forma assíncrona; responda rápido.
  console.log('Evento recebido:', event.type, event.id);

  return res.status(200).send('ok');
});
Use sempre crypto.timingSafeEqual (comparação em tempo constante) para evitar ataques de timing. Nunca compare assinaturas com === direto.

Idempotência no seu receptor

O mesmo evento pode chegar mais de uma vez (por reprocessamento interno ou por uma resposta sua que demorou demais). O seu endpoint deve ser idempotente.
1

Use o id da entrega como chave

Guarde o id do envelope (prefixo whd_) ao processar o evento. Ele é estável entre tentativas da mesma entrega.
2

Ignore o que já foi processado

Antes de aplicar efeitos colaterais, verifique se aquele id já foi processado. Se sim, responda 2xx e não faça nada de novo.
3

Responda rápido

Faça o trabalho pesado de forma assíncrona (fila/worker). Responda 2xx assim que validar a assinatura e registrar o evento.
O X-Webhook-Event-Id identifica o evento de origem e também pode ser usado para correlação e deduplicação. Já o id do envelope é específico da entrega ao seu endpoint.

Tempo limite e tentativas

Tempo limite

Cada POST tem um tempo limite de 30 segundos. Se o seu servidor não responder dentro desse prazo, a tentativa é considerada falha.

O que conta como sucesso

Apenas respostas HTTP na faixa 2xx são consideradas entrega bem-sucedida. Redirecionamentos, 4xx e 5xx contam como falha.
O Z2Pay registra o resultado de cada tentativa de entrega (status HTTP retornado pelo seu servidor, corpo da resposta e número de tentativas). A entrega só precisa que você responda 2xx para ser marcada como bem-sucedida.

Boas práticas

Sem secret, não há cabeçalhos de assinatura e você não consegue garantir que a requisição veio do Z2Pay. Configure um secret forte ao cadastrar o webhook em Core API: Webhooks.
Rejeite com 401 qualquer requisição cuja assinatura não bata. Não processe o evento antes de validar.
Enfileire o evento e responda 2xx rapidamente. Processamento lento aumenta o risco de estourar o tempo limite de 30s e gerar reentregas.
Em fluxos críticos (ex.: liberar um pedido), confirme o estado consultando a Core API: Transactions com o id recebido em data.

Veja também

Eventos

A lista de tipos de evento (type) que o Z2Pay envia.

Core API: Webhooks

Cadastrar, listar e remover endpoints de webhook.

Erros

Formato e códigos de erro da API.

Convenções

IDs com prefixo, valores em centavos e datas em ISO 8601.