Documentação
Ambiente de testes (Sandbox)
Antes de processar pagamento real, você quer testar sua integração — gerar PIX, confirmar webhook, fazer saque, lidar com rejeição. O Sandbox da Pagnovo é um ambiente paralelo onde tudo funciona como em produção, mas nenhum dinheiro real é movimentado.
A ideia é simples: você cria uma conta de testes em 20 segundos (sem KYC, sem cartão, sem CNPJ), recebe uma chave sk_test_* e usa os mesmos endpoints da API real. As transações são processadas por um acquirer interno que segue regras determinísticas baseadas no valor — você controla se quer simular um pagamento aprovado, rejeitado, chargeback ou estorno só mudando os centavos.
Quando estiver tudo funcionando, solicite produção pelo dashboard, complete o KYC e use a chave LIVE (sem prefixo) nos mesmos endpoints. Zero código pra trocar — só a chave muda.
Criar sua conta sandbox
- Acesse https://portal.pagnovo.com/auth/signup
- Preencha 6 campos (nome, e-mail, CPF, senha, tipo de uso, categoria) — sem CNPJ, sem upload de documento, sem aprovação manual
- Confirme seu e-mail pelo link que chega na caixa de entrada (válido por 48h)
- Faça login → o dashboard abre direto em modo TEST, com banner amarelo no topo avisando "Modo desenvolvedor"
- Sua chave
sk_test_*já está disponível em Configurações → Credenciais
Pronto. Daqui você pode chamar qualquer endpoint da API real.
Como autenticar
Mesma autenticação HTTP Basic da API normal — só o formato da chave muda:
| Ambiente | Formato da chave | Exemplo |
|---|---|---|
| TEST (Sandbox) | sk_test_<uuid> | sk_test_6c94c548-4bf6-4c05-919c-bf6d490f1eff |
| LIVE (Produção) | UUID puro (sem prefixo) | 6c94c548-4bf6-4c05-919c-bf6d490f1eff |
O prefixo sk_test_ é só pra você reconhecer visualmente que está usando a chave de teste — o backend identifica o ambiente pelo registro da chave no banco.
# Sandbox
curl -X POST "https://api.pagnovo.com/transactions/v2/purchase" \
-H "Authorization: Basic $(echo -n 'secret:sk_test_6c94c548-4bf6-4c05-919c-bf6d490f1eff' | base64)" \
-H "Content-Type: application/json" \
-d '{ "amount": 1000, "paymentMethod": "PIX", ... }'
Não pode usar a chave LIVE em sandbox e vice-versa — a API rejeita com 401.
Regras determinísticas — PIX (cash-in)
O acquirer do sandbox decide o status da transação baseado nos 2 últimos dígitos do valor em centavos. Isso permite simular qualquer cenário sem depender de timing real:
| Valor (centavos) | Final | Status retornado | Webhook V2 disparado |
|---|---|---|---|
1000 (R$ 10,00) | .00 | APPROVED | cashin.paid |
1001 (R$ 10,01) | .01 | REJECTED | nenhum evento V2 (só response HTTP) |
1002 (R$ 10,02) | .02 | INCOSISTENT | cashin.paid com status: "INCOSISTENT" no payload |
1004 (R$ 10,04) | .04 | CHARGEBACK | infraction.updated |
1005 (R$ 10,05) | .05 | REFUNDED | cashin.refunded |
1006 (R$ 10,06) | .06 | BLOCKED | infraction.updated |
Outros (.03, .07, .08, .09 etc.) | qualquer | APPROVED (default) | cashin.paid |
Os dois últimos dígitos do valor controlam o outcome. R$ 50,00, R$ 1.000,00 e R$ 0,03 todos retornam APPROVED.
⚠️ CHARGEBACK e BLOCKED disparam o evento
infraction.updatedem V2 (não eventos próprios). Os campos da disputa vêm no payload do mesmo evento — veja a seção de webhooks V2 pra detalhes.⚠️ REJECTED não dispara nenhum webhook V2 — só retorna no response HTTP. Se você usa V1 (
postbackUrlper-tx), o POST sai comstatus: "REJECTED"no payload V1 normal.
# Simula PIX rejeitado (final .01):
curl -X POST "https://api.pagnovo.com/transactions/v2/purchase" \
-H "Authorization: Basic $(echo -n 'secret:sk_test_xxx' | base64)" \
-d '{ "amount": 5001, "paymentMethod": "PIX", "name": "...", "email": "...", "cpf": "..." }'
# → response HTTP retorna status: REJECTED
# → webhook V2: nenhum disparo (REJECTED não tem mapping V2)
# → webhook V1: POST para postbackUrl com { type: "TRANSACTION", status: "REJECTED", ... }
Regras determinísticas — Withdraw (cash-out)
Pra saques, o outcome é decidido pelo CPF do recebedor (campo document no DTO de saque). Use CPFs especiais pra simular cada cenário:
| CPF do recebedor | Status retornado |
|---|---|
11111111111 | WITHDRAW_APPROVED |
99999999999 | WITHDRAW_REJECTED |
22222222222 | WITHDRAW_RETURNED |
| Qualquer outro CPF válido | WITHDRAW_APPROVED (default) |
# Simula saque rejeitado:
curl -X POST "https://api.pagnovo.com/withdraws/cash-out" \
-H "Authorization: Basic $(echo -n 'secret:sk_test_xxx' | base64)" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"pixKey": "99999999999",
"pixType": "CPF",
"document": "99999999999",
"externalId": "saque-teste-1"
}'
# → status: WITHDRAW_REJECTED no response + webhook cashout.failed
O
pixTypeé obrigatório e indica o tipo da chave PIX do recebedor:CPF,CNPJ,PHONE,RANDOM. O campodocumentcarrega o CPF/CNPJ do recebedor pra validação anti-fraude (independente dapixKey).
Webhooks no Sandbox
Você tem dois modelos de webhook que rodam em paralelo (e funcionam igual em Sandbox e em produção):
-
V2 (recomendado) — endpoints globais. Cadastre uma URL na aba Webhooks do dashboard (ou via
POST /v2/webhooks) escolhendo os eventos. Você recebe notificação de todos os eventos daquele tipo, sem precisar passarpostbackUrlem cada request. O payload vem com envelope{ event, environment, payload }. -
V1 (legado) —
postbackUrlpor transação. Você passa o campopostbackUrlno body de cadaPOST /transactions/v2/purchaseouPOST /withdraws/cash-out. Quando a transação muda de status, fazemos um POST naquela URL com o payload (camelCase plano, sem envelope).
Se você configurar os dois (postbackUrl numa transação + um endpoint V2 ativo no mesmo evento), seu servidor recebe 2 POSTs — um de cada modelo. Garanta que seu handler seja idempotente. Pra distinguir: o payload V2 tem os campos
eventeenvironment.
curl -X POST "https://api.pagnovo.com/transactions/v2/purchase" \
-H "Authorization: Basic $(echo -n 'secret:sk_test_xxx' | base64)" \
-H "Content-Type: application/json" \
-d '{
"name": "João Silva",
"email": "joao@exemplo.com",
"cpf": "12345678901",
"phone": "9876543210",
"amount": 1001,
"description": "Teste sandbox",
"responsibleDocument": "12345678901",
"responsibleExternalId": "user-1",
"externalId": "tx-test-1",
"paymentMethod": "PIX",
"postbackUrl": "https://webhook.site/<seu-uuid>"
}'
Em Sandbox, o webhook chega no mesmo formato que produção — você consegue testar parsing, validação de assinatura HMAC e handlers reais antes de subir.
Como diferenciar TEST de LIVE no seu lado:
-
Pela chave — toda request HTTP traz a chave que originou a transação:
sk_test_*indica teste, UUID puro indica produção. No seu handler de webhook, inspecione qual chave criou aquela Transaction (pode salvar no seu DB local). -
externalId— você passa um ID do seu banco aqui. Em ambiente de teste use prefixos diferentes (test-pedido-123vspedido-123) pra evitar conflito. -
Saldo isolado — Sandbox tem saldo fictício separado (R$ 100.000 inicial). Não confunde com produção.
Dica: a aba Webhooks do dashboard já tem CRUD de endpoints V2 com seleção de eventos, histórico de entregas e reenvio. Para novas integrações, prefira V2 — o
postbackUrlpor transação (V1) segue suportado para quem já usa.
Pra testar sem expor URL público, use webhook.site ou ngrok durante o desenvolvimento.
Saldo e isenção de taxas
Toda conta sandbox começa com saldo fictício (geralmente R$ 100.000) — não tem fluxo de "carregar saldo de teste". O acquirer mock retorna saldo simulado quando você consulta:
curl "https://api.pagnovo.com/accounts/balance" \
-H "Authorization: Basic $(echo -n 'secret:sk_test_xxx' | base64)"
# {
# "available": 10000000, // R$ 100.000 em centavos
# "blocked": 0,
# "totalBalance": 10000000
# }
Taxas zeradas — Sandbox roda com taxPix=0, taxWithdraw=0, taxRefund=0 e fees fixos = 0. Permite calcular fluxo bruto sem precisar normalizar os valores recebidos.
Quando você solicitar produção e o KYC for aprovado, o User LIVE herda as taxas comerciais que você negociar com a Pagnovo.
Resetar sandbox
Quer começar do zero? Configurações → Sandbox → Resetar sandbox apaga tudo que você criou em testes:
- ✅ Apaga: Transactions, Withdraws, Refunds, Customers, Invoices, Subscriptions, Products, Offers, Links de pagamento, Cupons
- ❌ Mantém: sua chave
sk_test_*, configurações da conta (módulos, webhook URL), e o registro do User TEST
A confirmação exige digitar RESETAR pra liberar o botão. Limite de 1 reset por 30 segundos — evita spam.
Também disponível via API:
curl -X POST "https://api.pagnovo.com/v2/sandbox/reset" \
-H "Authorization: Bearer <seu-jwt-do-dashboard>"
# {
# "ok": true,
# "deletedCounts": {
# "transactions": 12,
# "withdraws": 3,
# "invoices": 5,
# ...
# }
# }
Esse endpoint exige autenticação JWT do dashboard + 2FA (não pode chamar com sk_test_) — é destrutivo demais pra ser API-key only.
Solicitar produção (LIVE)
Quando sua integração estiver funcionando em Sandbox, é hora de pedir o ambiente LIVE. O dashboard tem um botão "Solicitar produção" no banner amarelo do topo (ou em qualquer página dentro do modo TEST).
Fluxo:
- Clica em "Solicitar produção" → abre o wizard
/auth/upgrade-to-liveem 5 passos - Preenche dados completos: endereço da empresa, dados do sócio, uploads (contrato social, cartão CNPJ, comprovantes)
- Submete pra análise → conta entra em status
ANALYZINGno LIVE - Equipe Pagnovo revisa (1-3 dias úteis) e aprova
- Após aprovação: você recebe uma nova chave LIVE (UUID puro, sem prefixo) e suas configurações de TEST são copiadas pro LIVE (categoria, módulos, webhook URL, etc)
- Toggle TEST↔LIVE no dashboard libera
Importante: catálogo, transações, customers e demais dados do TEST não são migrados pro LIVE. Sandbox é descartável — produção começa virgem.
Você pode continuar usando TEST em paralelo com LIVE — é útil pra testar mudanças antes de subir, ou pra reproduzir um bug específico.
Diferenças entre TEST e LIVE
| Aspecto | TEST (Sandbox) | LIVE (Produção) |
|---|---|---|
| Chave da API | sk_test_<uuid> | UUID puro |
| KYC obrigatório | ❌ Não | ✅ Sim (análise 1-3 dias) |
| Dinheiro real | ❌ Não | ✅ Sim |
| Saldo inicial | R$ 100.000 fictício | R$ 0 — depende dos cash-ins |
| Taxas | Zeradas | Conforme contrato |
| Acquirer | SandboxAcquiringProvider (mock) | PSPs reais (PIX da SPI, etc) |
| Webhooks | Disparados normalmente | Disparados normalmente |
| Limite de transações | Sem limite específico | Conforme acordo comercial |
| Reset | ✅ Botão no dashboard | ❌ Impossível |
| Banner amarelo no dashboard | ✅ "Modo desenvolvedor" | ❌ |
A regra geral: se você consegue fazer em TEST, vai funcionar igual em LIVE. O único cuidado é não esquecer de trocar a chave no .env quando subir pra produção.
Checklist de migração TEST → LIVE
Antes de virar a chave (literalmente), confere:
- Webhook URL acessível publicamente (não
localhost) e respondendo HTTP 200 em < 5s - Verificação de assinatura HMAC implementada — ver Verificação de assinatura
- Idempotência no seu handler — você pode receber o mesmo webhook 2-3x em casos de retry
-
externalIdpreenchido em todos os requests pra rastrear via dashboard depois - Logs capturando
x-trace-idde toda response da API — ver Trace ID e debug - Retry exponential backoff se você fizer polling de status (não recomendado — prefira webhook)
- Cenários de erro testados:
.02(rejected),.04(chargeback),.05(refunded) - Saque rejeitado testado com
document: '99999999999'(mesma chave PIX como destino) -
.envseparado entre staging/produção (não hardcoda chave em código)
Tudo OK? Solicita produção pelo dashboard e bora.