PayZuDocs

Webhooks

Em vez de ficar perguntando se o pagamento caiu, a gente avisa o seu servidor no instante em que o status muda, e insiste com novas tentativas espaçadas por até 72 vezes se o seu sistema não responder.

O que é um webhook (callback)

Um webhook (também chamado de callback) é uma requisição POST que a PayZu envia para o seu servidor quando algo acontece. Ao contrário da API normal (onde você chama a PayZu), aqui é o oposto: a PayZu chama você.

Pensa numa cobrança Pix. Você criou ela, exibiu o QR ao cliente, e agora precisa saber quando o cliente paga. Duas opções:

  1. Polling, ficar perguntando a cada X segundos "já pagou? já pagou?" (custoso, lento, desnecessário).
  2. Webhook, deixar a PayZu te avisar assim que o pagamento entrar (instantâneo, eficiente, recomendado).

Como configurar

Você pode receber as notificações de duas formas:

  • Webhook cadastrado (recomendado): registre uma URL persistente em POST /user/webhooks, com segredo HMAC e seleção de eventos. A mesma URL vale para todas as transações.
  • callbackUrl por transação: informe a URL no campo callbackUrl do body a cada transação criada:
{
  "amount": 99.90,
  "callbackUrl": "https://seusite.com.br/webhooks/payzu",
  "clientReference": "pedido-2025-001"
}

A PayZu vai enviar o callback para essa URL toda vez que aquela transação mudar de status (PENDING → COMPLETED, COMPLETED → REFUNDED, etc).

Crie um endpoint público no seu servidor

Algum lugar acessível pela internet que aceite POST com JSON. Exemplos: https://seusite.com.br/webhooks/payzu, https://api.suaempresa.com/payzu/callback.

Durante desenvolvimento local, use túneis como ngrok ou Cloudflare Tunnel pra expor o localhost.

Passe a URL ao criar a transação

Em todo POST /pix, POST /withdraw, POST /internal-transfer, inclua o campo callbackUrl. Pode ser a mesma URL pra todos.

Implemente o handler

Receba o POST, leia o JSON, processe e responda 2xx em até 5 segundos. Veja exemplos em Receber Pix · passo 3.

Header obrigatório no POST enviado pela PayZu: Content-Type: application/json.

Sistema de retry

Os webhooks da PayZu têm um sistema robusto de retentativa que garante a entrega mesmo em falhas temporárias. A PayZu reenvia até 72 vezes o mesmo callback com backoff exponencial e jitter, distribuindo melhor a carga e evitando picos de requisições.

Tempo de resposta: o webhook deve responder com um 2xx (por exemplo 200 ou 204) em até 5 segundos. Se exceder esse tempo, o sistema considera timeout e inicia o processo de retentativa.

Segurança

Para garantir integridade e segurança, restrinja o acesso ao seu endpoint de webhook. Solicite o IP oficial da PayZu Processamento ao suporte e aceite callbacks apenas dessa origem.

Verificação HMAC

Cada webhook é assinado com o seu webhook secret, definido ao cadastrar o webhook em POST /user/webhooks. Sua aplicação deve validar a assinatura antes de processar o payload:

Extraia os cabeçalhos x-webhook-timestamp, x-webhook-nonce e x-webhook-signature.

Concatene os valores do timestamp, do nonce e do payload, separados por ., formando a string base de verificação: timestamp.nonce.payload.

Gere uma assinatura HMAC com o algoritmo SHA-256 a partir dessa string, usando o seu webhook secret. O resultado é hexadecimal de 64 caracteres.

Compare a assinatura gerada com o valor do cabeçalho x-webhook-signature. Se não coincidirem, rejeite o webhook.

Exemplo em Node.js, usando crypto.timingSafeEqual para comparar as assinaturas em tempo constante:

const crypto = require("node:crypto");

function verifyWebhookSignature(request, webhookSecret) {
  const timestamp = request.headers["x-webhook-timestamp"];
  const nonce = request.headers["x-webhook-nonce"];
  const signature = request.headers["x-webhook-signature"];

  if (typeof signature !== "string" || !/^[0-9a-f]{64}$/i.test(signature)) {
    return false;
  }

  const baseString = `${timestamp}.${nonce}.${request.rawBody}`;
  const expectedSignature = crypto
    .createHmac("sha256", webhookSecret)
    .update(baseString)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature, "hex"),
    Buffer.from(signature, "hex"),
  );
}

Calcule o HMAC sobre o corpo cru da requisição, exatamente como recebido, antes de qualquer parse de JSON.

Campos do payload

Identificação

CampoTipoDescrição
idstringID da transação
clientReferencestringReferência externa que você forneceu
virtualAccountstringSubconta virtual (até 50 caracteres). Volta no callback para correlacionar lojas, filiais, marketplaces.
callbackUrlstringURL configurada para receber este webhook

Status e valores

CampoTipoDescrição
statusstringPENDING, COMPLETED, CANCELED, WAITING_FOR_REFUND, REFUNDED, EXPIRED, ERROR
typestringDEPOSIT, WITHDRAW
methodstringPIX, BANK_SLIP, INTERNAL_TRANSFER
amountnumberValor em BRL
serviceFeeChargednumberTarifa cobrada

Cobrança gerada (depósito)

CampoTipoDescrição
qrCodeTextstringCódigo Pix copia-e-cola
qrCodeUrlstringURL da imagem do QR Code
qrCodeBase64stringImagem do QR Code em formato Base64
generatedNamestringNome de referência
generatedDocumentstringCPF ou CNPJ
generatedEmailstringEmail vinculado à transação

Pagador

CampoTipoDescrição
payerNamestringNome do pagador
payerDocumentstringDocumento do pagador
payerInstitutionIspbstringISPB do banco do pagador
payerInstitutionNamestringNome do banco do pagador
payerAccountNumberstringConta PayZu do pagador (6 dígitos). Preenchida quando a conta PayZu é quem paga: saques e transferências internas.

Recebedor

CampoTipoDescrição
receiverNamestringNome do destinatário
receiverDocumentstringDocumento do destinatário
receiverInstitutionIspbstringISPB do banco do destinatário
receiverInstitutionNamestringNome do banco do destinatário
receiverAccountNumberstringConta PayZu do destinatário (6 dígitos). Preenchida quando a conta PayZu é quem recebe: depósitos e transferências internas.

Saque via chave Pix

CampoTipoDescrição
withdrawPixKeystringChave Pix usada no saque
withdrawPixTypestringcpf, cnpj, phone, email, evp

Liquidação e estorno

CampoTipoDescrição
endToEndIdstringEndToEnd ID do Pix
paidAtstringTimestamp do pagamento (ISO 8601)
cancellationReasonstringMotivo do cancelamento
refundEndToEndIdstringEndToEnd ID do estorno
refundAmountstringValor estornado
refundStatusstringPENDING, COMPLETED, CANCELED
refundReasonstringMotivo do estorno
refundDescriptionstringDescrição do estorno
refundedAtstringTimestamp do estorno (ISO 8601)

Timestamps

CampoTipoDescrição
createdAtstringTimestamp de criação (ISO 8601)
updatedAtstringTimestamp de atualização (ISO 8601)

Infração (disputa Pix)

CampoTipoDescrição
infractionobjectDetalhes da infração quando aberta (ver MED)

Boas práticas

  • Responda rápido: devolva 2xx em menos de 5s. Processe pesado em fila/worker, não no handler.
  • Idempotência: armazene id + status para deduplicar. O mesmo callback pode chegar mais de uma vez (retentativa, mudanças sucessivas).
  • Use clientReference: passe um identificador externo na criação da transação. Volta no callback e facilita correlacionar com seu pedido.
  • Restrinja por IP: aceite callbacks apenas do IP oficial da PayZu.
  • Não responda 4xx por erro de validação interna: a PayZu não retenta e o callback se perde.
  • Mascare payerDocument nos logs: imprimir o payload sem mascarar dados pessoais é risco LGPD.

Testar e reenviar

Testar localmente

Exponha seu localhost via ngrok ou Cloudflare Tunnel e dispare o payload manualmente:

curl -X POST https://seu-tunel.ngrok.io/webhooks/payzu \
  -H "Content-Type: application/json" \
  -d '{
    "id": "PAYZU20251123104518DF75D20A8F",
    "type": "DEPOSIT",
    "status": "COMPLETED",
    "amount": 99.90,
    "clientReference": "order-1234",
    "virtualAccount": "loja-rj-01",
    "paidAt": "2025-11-23T10:46:26.986Z"
  }'
await fetch('https://seu-tunel.ngrok.io/webhooks/payzu', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    id: 'PAYZU20251123104518DF75D20A8F',
    type: 'DEPOSIT',
    status: 'COMPLETED',
    amount: 99.90,
    clientReference: 'order-1234',
    virtualAccount: 'loja-rj-01',
    paidAt: '2025-11-23T10:46:26.986Z',
  }),
});
import requests

requests.post(
    'https://seu-tunel.ngrok.io/webhooks/payzu',
    headers={'Content-Type': 'application/json'},
    json={
        'id': 'PAYZU20251123104518DF75D20A8F',
        'type': 'DEPOSIT',
        'status': 'COMPLETED',
        'amount': 99.90,
        'clientReference': 'order-1234',
        'virtualAccount': 'loja-rj-01',
        'paidAt': '2025-11-23T10:46:26.986Z',
    },
)

Reenviar um callback real

Para reprocessar um callback que falhou no seu lado (depois de ter ajustado o handler), use os endpoints de reenvio:

Inspecionar o histórico

A PayZu guarda todas as tentativas de entrega. Útil para investigar falha:

Próximos passos

Nesta página