PagNow

Webhooks

Quando o status de uma cobrança ou saque muda, enviamos um POST para o seu endpoint. Configure um endpoint fixo no dashboard (Webhooks) ou via API.

Eventos

Há 5 eventos canônicos:

EventoQuando ocorre
payment.completedCobrança paga e confirmada (PIX/cartão liquidado).
payment.failedCobrança falhou, expirou ou foi cancelada.
payment.refundedCobrança paga e posteriormente reembolsada.
withdrawal.completedSaque/payout liquidado com sucesso.
withdrawal.failedSaque/payout falhou ou foi rejeitado.

Aliases legados aceitos na subscrição (expandidos internamente):

AliasEquivale a
payment.status_changedpayment.completed + payment.failed
withdrawal.status_changedwithdrawal.completed + withdrawal.failed

Os aliases existem para compatibilidade retroativa. Prefira subscrever pelos nomes canônicos. Use GET /v1/webhooks/event-catalog para listar os eventos disponíveis em tempo real (ver abaixo).

Headers enviados

X-PagNow-Event-Type: payment.completed
X-PagNow-Delivery-Id: <uuid>
X-PagNow-Signature: sha256=<hmac_hex>
Content-Type: application/json

Corpo

Sempre no formato { event, data }. O nome do evento está em event e o transactionId identifica a transação.

{
  "event": "payment.completed",
  "data": {
    "transactionId": "a99e3d82-352b-49b2-b10e-4551ca69446e",
    "amount": 1990,
    "status": "PAID",
    "previousStatus": "WAITING_PAYMENT",
    "paidWith": "PIX",
    "providerFee": 30,
    "platformFee": 40,
    "netAmount": 1920,
    "occurredAt": "2026-05-27T12:34:56.000Z"
  }
}

Campos de taxa (providerFee/platformFee/netAmount) e paidWith só aparecem quando aplicáveis.

Verificação de assinatura

A assinatura é o HMAC-SHA256 do corpo cru (bytes recebidos, sem re-serializar) usando o secret do seu endpoint, no formato sha256=<hex>. O secret é uma string hexadecimal de 64 caracteres (exemplo: b8f3a9c1d4e5...). Sempre valide antes de confiar no payload.

import { createHmac, timingSafeEqual } from 'crypto';

function verify(rawBody, header, secret) {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(header ?? '');
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}

// Express — use o corpo CRU (raw), não o JSON já parseado:
app.post('/webhooks/pagnow', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verify(req.body, req.headers['x-pagnow-signature'], endpointSecret)) {
    return res.status(401).end();
  }
  const { event, data } = JSON.parse(req.body.toString('utf8'));
  if (event === 'payment.completed') {
    // data.transactionId — libere o pedido
  }
  res.status(200).end();
});

Os SDKs já trazem isso pronto: pagnow.webhooks.verify(rawBody, signature, secret) (Node) e $pagnow->webhooks->verify($raw, $sig, $secret) (PHP). Veja SDKs.

Retentativas e status de entrega

Em caso de falha (resposta não-2xx ou timeout), reenviamos com backoff exponencial: 2^tentativa segundos, com teto de 5 minutos. O máximo de tentativas é 10. Após esgotar o orçamento, a entrega vai para CANCELLED e pode ser reenviada manualmente (ver abaixo).

StatusDescrição
PENDINGAguardando processamento.
PROCESSINGEm tentativa de envio no momento.
DELIVEREDRecebida com resposta 2xx.
FAILEDÚltima tentativa falhou; nova retentativa agendada.
CANCELLEDEsgotou todas as tentativas. Use o replay para reenviar.

Gerenciando endpoints

Todos os endpoints abaixo são prefixados com /v1/webhooks e autenticados via apikey header (Kong).

Criar endpoint

POST /v1/webhooks/endpoints
{
  "url": "https://minha-app.com/webhooks/pagnow",
  "events": ["payment.completed", "payment.failed", "payment.refunded"]
}

Resposta (201):

{
  "id": "whe_01j...",
  "url": "https://minha-app.com/webhooks/pagnow",
  "events": ["payment.completed", "payment.failed", "payment.refunded"],
  "secret": "b8f3a9c1d4e57f2a...",
  "status": "ACTIVE"
}

O secret é retornado apenas na criação. Guarde-o com segurança — não é possível recuperá-lo depois (somente rotacionar).

Listar endpoints

GET /v1/webhooks/endpoints

Retorna { data: [...] }. O secret não é incluído na listagem.

Editar endpoint

PATCH /v1/webhooks/endpoints/{id}

Campos opcionais: url, events, status (ACTIVE ou INACTIVE). Use status: "INACTIVE" para desativar temporariamente sem deletar.

{ "events": ["payment.completed", "payment.failed", "payment.refunded", "withdrawal.completed", "withdrawal.failed"] }

Resposta: { id, url, events, status }.

Deletar endpoint

DELETE /v1/webhooks/endpoints/{id}

Resposta: { "deleted": true }.

Rotacionar secret

POST /v1/webhooks/endpoints/{id}/rotate-secret

Body: { "id": "<endpoint_id>" }.

Resposta: { "id": "whe_01j...", "secret": "f3c9a2d1..." }.

O secret anterior para de funcionar imediatamente. Atualize seu receptor antes de rotacionar em produção.

Catálogo de eventos

GET /v1/webhooks/event-catalog

Retorna os 5 eventos canônicos com descrições:

[
  { "event": "payment.completed", "description": "Cobrança paga e confirmada (PIX/cartão liquidado)." },
  { "event": "payment.failed",    "description": "Cobrança falhou, expirou ou foi cancelada." },
  { "event": "payment.refunded",  "description": "Cobrança paga e posteriormente reembolsada." },
  { "event": "withdrawal.completed", "description": "Saque/payout liquidado com sucesso." },
  { "event": "withdrawal.failed",    "description": "Saque/payout falhou ou foi rejeitado." }
]

Estatísticas

GET /v1/webhooks/stats

Retorna contagem de entregas por status para o tenant.

Histórico de entregas e reenvio

Listar entregas

GET /v1/webhooks/deliveries?status=FAILED&eventType=payment.completed&endpointId=whe_01j...&limit=25&offset=0

Todos os parâmetros são opcionais. Retorna { data, total, limit, offset }.

Detalhe de uma entrega

GET /v1/webhooks/deliveries/{id}

Inclui:

  • logs[] — uma entrada por tentativa, com statusCode, error, responseBody (corpo da resposta do seu servidor) e duration (ms).
  • endpointurl e status do endpoint no momento da consulta.
  • payload — o JSON enviado.
  • lastStatusCode, lastError, lastResponseBody — resultado da última tentativa.

Reenviar uma entrega

POST /v1/webhooks/deliveries/{id}/replay

Body: { "id": "<delivery_id>" }.

Recoloca a entrega na fila (status PENDING, tentativas zeradas). Aceita entregas em status FAILED ou CANCELLED.

Reenvio em lote

POST /v1/webhooks/deliveries/replay-bulk

Body opcional: { "status": "FAILED", "endpointId": "whe_01j...", "limit": 50 }.

Sem filtros, recoloca todas as entregas FAILED e CANCELLED do tenant (máximo 500). Retorna { "requeued": <n> }.

Boas práticas

  • Responda 2xx rápido; o timeout de entrega é 30 segundos.
  • Use o corpo cru na verificação (não o JSON re-serializado).
  • Trate entregas duplicadas de forma idempotente (use data.transactionId + X-PagNow-Delivery-Id).
  • Monitore entregas CANCELLED via GET /v1/webhooks/stats e use o replay-bulk para recuperar lotes após indisponibilidade do seu servidor.

Nesta página