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:
| Evento | Quando ocorre |
|---|---|
payment.completed | Cobrança paga e confirmada (PIX/cartão liquidado). |
payment.failed | Cobrança falhou, expirou ou foi cancelada. |
payment.refunded | Cobrança paga e posteriormente reembolsada. |
withdrawal.completed | Saque/payout liquidado com sucesso. |
withdrawal.failed | Saque/payout falhou ou foi rejeitado. |
Aliases legados aceitos na subscrição (expandidos internamente):
| Alias | Equivale a |
|---|---|
payment.status_changed | payment.completed + payment.failed |
withdrawal.status_changed | withdrawal.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/jsonCorpo
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).
| Status | Descrição |
|---|---|
PENDING | Aguardando processamento. |
PROCESSING | Em tentativa de envio no momento. |
DELIVERED | Recebida com resposta 2xx. |
FAILED | Última tentativa falhou; nova retentativa agendada. |
CANCELLED | Esgotou 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/endpointsRetorna { 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-secretBody: { "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-catalogRetorna 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/statsRetorna 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=0Todos 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, comstatusCode,error,responseBody(corpo da resposta do seu servidor) eduration(ms).endpoint—urlestatusdo endpoint no momento da consulta.payload— o JSON enviado.lastStatusCode,lastError,lastResponseBody— resultado da última tentativa.
Reenviar uma entrega
POST /v1/webhooks/deliveries/{id}/replayBody: { "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-bulkBody 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
2xxrá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
CANCELLEDviaGET /v1/webhooks/statse use o replay-bulk para recuperar lotes após indisponibilidade do seu servidor.
