# NewPag — API para integração
> NewPag é um gateway de pagamentos Pix para o Brasil. Este documento
> descreve a API pública em formato legível por modelos de linguagem
> (padrão llms.txt), para que assistentes de IA ajudem a integrar.
>
> Base da API: https://app.newpag.app. As chaves de API são geradas no painel
> em Desenvolvedores > Chaves de API.
## Visão geral
- Base URL: https://app.newpag.app/api/v1
- Formato: JSON (UTF-8). Valores monetários em CENTAVOS (inteiro). Ex.: R$ 19,90 = 1990.
- Datas: ISO 8601 (UTC).
- Métodos suportados hoje: Pix (cash-in, com estorno total via API) e saque Pix. Cartão e checkout hospedado vêm a seguir.
- Toda resposta traz um `request_id` (`req_…`) no corpo JSON e no header
`x-request-id`. Guarde-o nos seus logs: ele identifica a requisição na tela
"Logs da API" do painel (Integrações > Logs da API, retenção 30 dias) e no
suporte. Requisições autenticadas ficam registradas lá com status, duração e
código de erro — útil para depurar a integração sem abrir chamado.
## Autenticação
HTTP Basic auth: usuário = chave pública (`pk_live_…`), senha = segredo
(`sk_live_…`). Gere no painel; a pública fica sempre visível e o segredo é
mostrado uma única vez.
```
Authorization: Basic base64(pk_live_...:sk_live_...)
# cURL: curl ... -u "pk_live_...:sk_live_..."
```
- A credencial identifica a SUA empresa — cada cobrança é criada para a sua conta.
- Nunca exponha o segredo no navegador nem em repositórios versionados.
- Revogue credenciais comprometidas no painel (revogação é imediata).
- Legado: `Authorization: Bearer sk_live_...` (só o segredo) ainda funciona
(aceita apenas segredos nossos: `sk_live_...`/`sk_test_...`).
- Allowlist de IP (opcional, painel > Chaves de API): se configurada, requisições
de IPs fora da lista recebem `401` — mesmo com a credencial correta.
## Modo de teste (sandbox)
Crie uma chave de TESTE no painel (Chaves de API > Criar chave > ambiente
"Teste") — credenciais `pk_test_…`/`sk_test_…`. Com elas você integra sem
mover dinheiro real:
- Cobranças de teste ficam num ambiente separado — nunca aparecem no saldo,
dashboards ou extrato. Nenhum provedor Pix é chamado; o `pix.copyPaste`
retornado é um marcador NÃO-pagável.
- Mesmos endpoints e mesmos formatos de resposta. Toda resposta traz
`livemode: false` (chaves de produção trazem `livemode: true`).
- Simule o pagamento: `POST /api/v1/test/charges/{id}/pay` (só aceita chave de
teste). A cobrança vira `paid`, a taxa é calculada com as taxas REAIS da sua
conta e o webhook `transaction.paid` é disparado com `livemode: false` no
`data` — filtre pelo campo antes de dar baixa em pedidos reais.
- Chave de teste pode ser criada ANTES da aprovação do cadastro (KYC) — dá para
integrar enquanto a conta está em análise.
- Não suportado em teste: `splits` (400), saques/`transfers` (403
`test_mode_unsupported`). `GET /balance` responde zeros com `livemode: false`.
```
# criar cobrança de teste
curl -X POST https://app.newpag.app/api/v1/charges \
-u "pk_test_...:sk_test_..." \
-H "Content-Type: application/json" \
-d '{"amountCents":1990,"customer":{"name":"Cliente","email":"c@ex.com","document":"00000000000"}}'
# simular o pagamento (dispara o webhook transaction.paid com livemode:false)
curl -X POST https://app.newpag.app/api/v1/test/charges/{id}/pay \
-u "pk_test_...:sk_test_..."
```
## Recursos
### Criar uma cobrança Pix
```
POST /api/v1/charges
Authorization: Basic base64(pk_live_...:sk_live_...)
Content-Type: application/json
Idempotency-Key: pedido-1234 (opcional, recomendado — retry seguro)
{
"amountCents": 1990,
"customer": {
"name": "Cliente Exemplo",
"email": "cliente@exemplo.com",
"document": "00000000000",
"phone": "11987654321"
},
"description": "Pedido #1234",
"external_reference": "pedido-1234"
}
```
`customer.phone` (opcional, DDD + número, com ou sem máscara/+55): telefone do pagador.
Recomendado — alguns adquirentes exigem; sem ele, usamos o telefone do titular da conta.
Telefone inválido → `400 validation`.
`external_reference` (opcional, até 120 chars): o ID do pedido NO SEU sistema —
volta em toda resposta/webhook da cobrança e é pesquisável
(`GET /api/v1/charges?external_reference=pedido-1234`). Não precisa ser único.
Resposta `201 Created`:
```
{
"id": "clx...",
"livemode": true,
"status": "pending",
"amountCents": 1990,
"external_reference": "pedido-1234",
"pix": {
"copyPaste": "00020101br.gov.bcb.pix...",
"qrCodeBase64": "data:image/png;base64,iVBORw0..."
},
"createdAt": "2026-07-19T21:00:00.000Z"
}
```
Use `pix.copyPaste` para o copia-e-cola e `pix.qrCodeBase64` (PNG data-URL,
pronto para `
`) para exibir o QR ao pagador — sem gerar QR no seu lado.
Ambos vêm no `201` e no `GET /charges/{id}` (a listagem não os inclui).
`pix.copyPaste` é um payload **EMV** (padrão BR Code do BCB): uma sequência de campos, cada um com id de 2 dígitos, tamanho de 2 dígitos e valor. O campo `54` é o valor da cobrança quando o QR é de valor fixo; o campo `26` traz a chave Pix. Trate o código como opaco e repasse sem alterar.
### Consultar o status de uma cobrança
```
GET /api/v1/charges/{id}
Authorization: Basic base64(pk_live_...:sk_live_...)
```
Resposta `200 OK` com o mesmo formato acima, mais os dados de conciliação
(preenchidos quando `status = paid`):
```
{
"id": "clx...", "livemode": true, "status": "paid", "amountCents": 1990,
"external_reference": "pedido-1234", "description": "Pedido #1234",
"customer": { "name": "Cliente Exemplo", "email": "cliente@exemplo.com", "document": "00000000000" },
"payer": { "name": "Fulano de Tal", "document": "10900327570", "bank": "NU PAGAMENTOS - IP" },
"pix": { "copyPaste": "...", "qrCodeBase64": "...", "endToEnd": "E18236120202609130426s1894658118" },
"paidAt": "2026-09-13T04:26:13.927Z",
"createdAt": "2026-09-13T04:25:54.018Z"
}
```
- `customer` = o que VOCÊ informou ao criar a cobrança.
- `payer` = quem DE FATO pagou o Pix (nome, CPF/CNPJ e banco informados pelo
PSP na confirmação). `null` enquanto pendente ou se o PSP não informar.
- `pix.endToEnd` = End-to-end ID do Pix (identificador único da liquidação no
SPI) — use-o com `payer` para conciliar com o extrato bancário.
- Documentos vêm completos (só dígitos). A listagem (`GET /api/v1/charges`)
traz os mesmos campos, exceto `pix.copyPaste`/`qrCodeBase64`.
- `refund` = estorno da cobrança, se houver (`null` sem estorno):
`{ id, status: pending|completed|failed, amountCents, reason, createdAt, completedAt, failureReason }`.
Só no GET por id — ver "Estornar uma cobrança".
Faça polling do `status` até `paid` — ou, melhor, assine o webhook `transaction.paid`.
Status possíveis: `pending`, `paid`, `expired`, `canceled`, `refunded`,
`under_review`, `blocked`.
### Split de pagamentos (entre sellers do sistema)
Envie `splits` no corpo de `POST /api/v1/charges`. Quando o pagamento confirma, cada
recebedor é creditado no saldo dele; a reserva incide só sobre o que sobra para você.
`recipient_id` = id da empresa recebedora. `PERCENTAGE` usa `percent` (% do líquido);
`FIXED` usa `amount` (centavos). Soma dos percentuais ≤ 100. Split inválido → `400`.
```
"splits": [
{ "recipient_id": "id-do-seller-B", "type": "PERCENTAGE", "percent": 40 },
{ "recipient_id": "id-do-seller-C", "type": "FIXED", "amount": 1500 }
]
```
### Listar cobranças (filtros + paginação por cursor)
```
GET /api/v1/charges?limit=50&status=paid&external_reference=pedido-1234&from=2026-07-01&to=2026-07-31&starting_after=
Authorization: Basic base64(pk_live_...:sk_live_...)
```
Todos os parâmetros são opcionais. `status`: pending|paid|expired|canceled|under_review|refunded|blocked.
`from`/`to`: datas ISO. `starting_after`: id da última cobrança da página anterior (cursor).
Resposta `200`: `{ "data": [ { id, status, amountCents, description, external_reference, customer, createdAt } ], "has_more": true }`.
Enquanto `has_more` for true, repita com `starting_after` = id do último item.
### Saldo
```
GET /api/v1/balance
Authorization: Basic base64(pk_live_...:sk_live_...)
```
Resposta `200`: `{ "available": 12345, "reserved": 0, "currency": "BRL" }` (centavos).
### Dados da conta
```
GET /api/v1/company
Authorization: Basic base64(pk_live_...:sk_live_...)
```
Resposta `200` (objeto plano; `document` vem mascarado):
`{ id, legal_name, trade_name, document, status, kyc_status,
balance: { available, reserved, currency }, fees: { pixPercent, pixFixedCents,
reservePercent, reserveDays, withdrawalPercent, withdrawalFixedCents }, created_at }`.
### Solicitar saque (Pix out)
```
POST /api/v1/transfers
Authorization: Basic base64(pk_live_...:sk_live_...)
Content-Type: application/json
Idempotency-Key: saque-1234 (opcional, recomendado — retry seguro, evita saque duplicado)
{ "amount": 500, "pix_key": "12345678909", "pix_key_type": "CPF", "message": "opcional" }
```
**Saque por QR Code** (copia-e-cola Pix, payload EMV) — no lugar de `pix_key`/`pix_key_type`:
```
POST /api/v1/transfers
{ "qr_code": "00020126580014br.gov.bcb.pix0136...520400005303986540510.005802BR..." }
```
- QR de valor fixo: `amount` é opcional. Se informado, tem que bater com o valor do QR.
- QR de valor variável: `amount` é obrigatório (valor em centavos).
- Não envie `qr_code` junto com `pix_key`/`pix_key_type`.
- O destinatário recebe o valor do QR; a taxa é debitada **por cima** (saldo sai por valor + taxa).
No saque por chave, a taxa sai do próprio `amount`.
- Só alguns provedores aceitam QR. Em outro, a resposta é `422 transfer_failed` e nada é debitado.
- No retorno, `pix_key` traz o código do QR e `pix_key_type` é `qrcode`.
`pix_key_type`: `CPF`, `CNPJ`, `EMAIL`, `PHONE` ou `EVP` (chave aleatória, formato UUID). `message` (opcional): observação livre. Resposta `201`:
`{ "data": { id, status, amount, net_amount, fee, pix_key, pix_key_type, pending_approval, created_at } }`.
Com "saque automático" desligado na conta, o saque nasce pendente de aprovação
(`pending_approval: true`). Consulte com `GET /api/v1/transfers/{id}` ou liste em
`GET /api/v1/transfers?status=completed&from=...&to=...&starting_after=` —
mesma paginação por cursor das cobranças (resposta inclui `has_more`).
Envie `Idempotency-Key` (único por saque): no retry, o MESMO saque é devolvido em
vez de debitar o saldo de novo. Se o saque via API estiver desabilitado para a sua
conta, o retorno é `403 api_withdrawal_disabled`.
### Estornar uma cobrança (refund)
Devolve o valor ao pagador via Pix. **Total, uma vez por cobrança**, só para
cobrança `paid`, até 90 dias após o pagamento.
```
POST /api/v1/charges/{id}/refund
Authorization: Basic base64(pk_live_...:sk_live_...)
Content-Type: application/json
{ "reason": "Cliente desistiu" } (opcional, até 140 caracteres — o pagador pode ver)
```
Resposta `202` (PSP aceitou; a devolução está em processamento):
```
{ "id": "clx...", "chargeId": "clx...", "livemode": true, "status": "pending",
"amountCents": 1990, "reason": "Cliente desistiu",
"createdAt": "...", "completedAt": null, "failureReason": null }
```
O estorno é ASSÍNCRONO. `202 pending` significa que o PSP aceitou o pedido — o
dinheiro ainda não voltou. Ouça os webhooks:
- `transaction.refunded` — devolução concluída; a cobrança passa a `refunded`.
- `transaction.refund_failed` — o PSP recusou; a cobrança continua `paid` e o
valor retido volta ao seu saldo.
Se a devolução concluir na hora (pagador na mesma instituição), a resposta já
vem `200` com `status: "completed"`.
Enquanto o estorno está `pending`, o valor líquido fica RETIDO do seu saldo
disponível (não pode ser sacado). Repetir o `POST` na mesma cobrança é
idempotente: devolve o mesmo estorno, sem "estornar de novo". Um estorno
`failed` pode ser pedido de novo.
Consulte com `GET /api/v1/charges/{id}/refund` (mesmo objeto; `404` sem estorno)
ou pelo campo `refund` de `GET /api/v1/charges/{id}`.
Em modo de teste (chave `pk_test_`), o estorno conclui na hora, sem PSP, e a
cobrança de sandbox passa a `refunded`.
Recusas (`422`): `charge_not_refundable` (não está `paid`), `refund_window_expired`
(mais de 90 dias), `insufficient_balance` (saldo não cobre — o corpo traz
`shortfallCents`), `refund_not_supported` (o provider desta cobrança não oferece
estorno via API). `502 refund_failed`: o PSP recusou ou não respondeu; nada fica
retido.
## Erros
Respostas de erro usam JSON `{ "error": "", "message": "...", "request_id": "req_..." }`
(cite o `request_id` ao abrir chamado — ele aparece também em Integrações > Logs da API):
- `401 unauthorized` — chave ausente, inválida, revogada — ou IP fora da allowlist da conta.
- `400 validation` — corpo inválido (ver `details`).
- `403 account_inactive` — conta em análise ou bloqueada.
- `403 api_withdrawal_disabled` — saque via API desabilitado para esta conta.
- `403 test_mode_unsupported` — recurso indisponível com chave de teste (ex.: saques).
- `403 test_mode_only` — endpoint de simulação chamado com chave de produção.
- `404 not_found` — cobrança/saque não encontrado.
- `422 transfer_failed` — saque recusado (ex.: saldo insuficiente).
- `422 charge_not_refundable` / `refund_window_expired` / `insufficient_balance` /
`refund_not_supported` — estorno recusado (ver "Estornar uma cobrança").
- `502 refund_failed` — o PSP recusou ou não respondeu ao estorno; a retenção foi liberada.
- `409 idempotency_conflict` — a `Idempotency-Key` já foi usada com um corpo
DIFERENTE. A mesma chave só vale para retry do MESMO pedido; use outra chave.
- `429 rate_limited` — muitas requisições; aguarde o valor (segundos) do header
`retry-after` e tente de novo com backoff. O limite é por conta. Toda resposta
autenticada traz os headers `x-ratelimit-limit`, `x-ratelimit-remaining` e
`x-ratelimit-reset` (epoch em segundos) — monitore-os para não tomar 429.
- `502 charge_failed` — falha ao gerar a cobrança no provedor.
## Exemplo (curl)
```
curl -X POST https://app.newpag.app/api/v1/charges \
-u "pk_live_...:sk_live_..." \
-H "Content-Type: application/json" \
-d '{"amountCents":1990,"customer":{"name":"Cliente","email":"c@ex.com","document":"00000000000"}}'
```
## Webhooks
Cadastre endpoints em Integrações > Webhooks. A cada evento assinado enviamos um
`POST` JSON para a sua URL, com os headers `x-webhook-event` e
`x-webhook-signature: sha256=`.
Reenviamos até 3 vezes; responda `2xx` para confirmar. Corpo: `{ event, data, createdAt }`.
`data` dos eventos `transaction.*` (mesmo shape do `GET /api/v1/charges/{id}`):
```
{
"event": "transaction.paid",
"data": {
"id": "clx...", "livemode": true,
"amount": 5000, "net": 4670, "fee": 330, "method": "pix", "status": "paid",
"external_reference": "pedido-1234", "description": "Pedido #1234",
"customer": { "name": "Cliente Exemplo", "email": "cliente@exemplo.com", "document": "00000000000" },
"payer": { "name": "Fulano de Tal", "document": "10900327570", "bank": "NU PAGAMENTOS - IP" },
"pix": { "endToEnd": "E18236120202609130426s1894658118" },
"paidAt": "2026-09-13T04:26:13.927Z"
},
"createdAt": "2026-09-13T04:26:14.100Z"
}
```
`payer` (quem de fato pagou) e `pix.endToEnd` vêm do PSP na confirmação — são os
campos para conciliar com o extrato. Em `transaction.pending` ambos são `null`;
`net`/`fee` só existem em `transaction.paid`. Eventos do sandbox trazem
`livemode: false` e `payer: null`.
Eventos disponíveis:
- `transaction.pending` — Aguardando pagamento (cobrança criada).
- `transaction.paid` — Pago.
- `transaction.refunded` — Reembolsado (a devolução ao pagador concluiu; `data.status` = `refunded`).
- `transaction.refund_failed` — Estorno recusado pelo PSP (a cobrança segue `paid`; o valor retido voltou ao saldo).
- `withdrawal.processing` — Saque processando.
- `withdrawal.completed` — Saque concluído.
- `withdrawal.failed` — Saque falhou.
MED (contestações Pix — disparados quando o seller tem MED ativo):
- `dispute.opened` — infração aberta (responda a defesa no painel > Disputas dentro do prazo).
- `dispute.accepted` — defesa aceita (disputa ganha).
- `dispute.rejected` — defesa rejeitada (disputa perdida).
### Gerenciar endpoints via API
Além do painel, os endpoints podem ser geridos pela própria API (funciona com
chave de produção E de teste — o endpoint é o mesmo; eventos de teste chegam
com `livemode: false` no `data`):
```
GET /api/v1/webhooks → { "data": [ { id, url, events, status, secret_masked, ... } ] }
POST /api/v1/webhooks → cria; corpo: { "url": "https://...", "events": ["transaction.paid"], "description"?: "..." }
resposta 201 inclui `secret` (assinatura HMAC) UMA ÚNICA VEZ — guarde-o.
PATCH /api/v1/webhooks/{id} → { "active": true|false } pausa/reativa
DELETE /api/v1/webhooks/{id} → remove o endpoint
```
Regras: URL obrigatoriamente `https://` e pública (rede interna → `400
invalid_url`); eventos devem ser da lista acima; máximo de 20 endpoints por
conta (`400 endpoint_limit`).
## Boas práticas
- Trate valores sempre em centavos (inteiros).
- Prefira webhooks para saber que uma cobrança foi paga; use polling do status (GET) como alternativa.
- Envie `Idempotency-Key` (único por pedido) ao criar: no retry, a MESMA cobrança é
devolvida em vez de gerar outro Pix. Reusar a chave com um corpo DIFERENTE
retorna `409 idempotency_conflict` (funciona igual no modo de teste).
- Use `description` para a sua referência (ex.: "Pedido #1234"); ela fica salva na cobrança.
## Em breve
- Checkout hospedado (link de pagamento).
- Cartão de crédito, boleto, 3DS e assinaturas (exigem engines próprias).