FFC Business & Finance · API
Documentação da API de pagamentos. Pix, cartão, boleto, internacional, cripto e cobrança recorrente, com webhooks e saques.
Comece aqui em 5 minutosComece por aqui
Integre em 5 minutos. Você vai precisar de uma chave de API (aba Chaves, que exige KYC aprovado e 2FA ativado).
- Crie uma chave de teste (
nvr_dev_). Ela opera no sandbox, sem dinheiro real. - Crie uma cobrança Pix e mostre o QR/copia-e-cola ao seu cliente:
curl -X POST https://paynuvra.com/api/v1/charges \
-H "Authorization: Bearer nvr_dev_SUA_CHAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{ "amount": 5000, "externalReference": "pedido-123", "customer": { "name": "Maria", "document": "12345678909" } }'- Assine um webhook (aba Webhooks) para receber
charge.paidquando o cliente pagar. - Trocou pra produção? Gere uma chave
nvr_live_e use a mesma basehttps://paynuvra.com/api/v1.
É isso. As seções abaixo detalham autenticação, cada recurso, erros e webhooks.
Autenticação
Gere a chave em Desenvolvedor → Chaves de API e envie no header Authorization como Bearer token. Chaves nvr_dev_ operam em homologação (sandbox); nvr_live_ em produção. Base: https://paynuvra.com/api/v1.
curl https://paynuvra.com/api/v1/balance \ -H "Authorization: Bearer nvr_live_SUA_CHAVE_AQUI"
Sem chave válida → 401 unauthorized. Excesso de requisições → 429 rate_limited.
Cada chave carrega permissões granulares. Ao criar, o escopo pré-marca a lista e você ajusta. payout:create é sensível (envia dinheiro) e só entra por marcação explícita.
| Permissão | O que permite |
|---|---|
charge:create | Criar cobranças. Gera novas cobranças (Pix, cartão, boleto). |
charge:read | Consultar cobranças. Lê cobranças e o status de pagamento. |
charge:refund | Estornar cobranças. Estorna (reembolsa) uma cobrança paga. DEVOLVE dinheiro ao comprador e debita seu saldo; só marque se a integração realmente precisa estornar. |
payout:create | Criar saques. Emite saques Pix. ENVIA dinheiro para fora da conta; só marque se a integração realmente precisa sacar. |
payout:read | Consultar saques. Lê os saques e o status de cada um. |
bill_pay:create | Pagar boletos. Paga boletos de terceiros. ENVIA dinheiro para fora da conta; só marque se a integração realmente precisa pagar boletos. |
bill_pay:read | Consultar pagamentos de boleto. Lê os pagamentos de boleto e o status de cada um. |
order:create | Criar pedidos. Cria pedidos pela API. |
order:read | Consultar pedidos. Lê os pedidos da conta. |
balance:read | Consultar saldo. Lê o saldo disponível e a receber. |
webhook:create | Criar webhooks. Cadastra endpoints para receber eventos. |
webhook:read | Consultar webhooks. Lê os webhooks configurados e as entregas. |
webhook:update | Editar webhooks. Altera URL, eventos ou status de um webhook. |
webhook:delete | Remover webhooks. Exclui webhooks (ação destrutiva). |
Criar cobrança (Pix)
/api/v1/chargesIdempotente por externalReference: reenviar a mesma referência com o mesmo payload devolve a cobrança existente (200) em vez de criar outra (201). A adquirente não é chamada de novo.
Reenviar a mesma externalReference com um payload diferente (outro valor, método, documento ou e-mail) responde 409 idempotency_error. Nunca devolvemos a cobrança antiga em silêncio (isso mascararia, por exemplo, reusar a referência de um R$10 numa cobrança de R$1.000). Para uma cobrança nova, use uma referência nova; para repetir, mande o payload idêntico.
| Campo | Tipo | Descrição |
|---|---|---|
| amount | int (centavos) | Obrigatório. Mín. 100 (R$1,00). |
| customer.name | string | Obrigatório. |
| customer.document | string | Obrigatório. CPF ou CNPJ. |
| customer.email | string? | Opcional no Pix; OBRIGATÓRIO no cartão. |
| customer.phone | string? | Opcional no Pix; OBRIGATÓRIO no cartão. Aceito em QUALQUER formato BR, com ou sem +55, com ou sem pontuação (ex.: (11) 99999-8888, 11999998888, +5511999998888). Normalizamos para E.164; precisa ser um número BR válido (DDD + 8 dígitos fixo ou 9 celular), senão é tratado como ausente. Devolvido normalizado em order.*/cart.abandoned; habilita recuperação por WhatsApp. |
| externalReference | string | Obrigatório (máx. 128). Sua chave de idempotência. |
| description | string? | Opcional (máx. 200). No cartão, define o NOME NA FATURA (statement/soft descriptor) desta transação; deve IDENTIFICAR o seu negócio. É sanitizado (MAIÚSCULAS, sem acento, só A-Z 0-9 e espaço) e cortado no limite da adquirente (cerca de 13 caracteres; até 22 conforme o tipo de conta). IMPORTANTE: a linha do extrato é COMPOSTA pela adquirente com o nome do recebedor cadastrado, então este campo ajusta o SUFIXO, não substitui o nome inteiro. Se ausente, usa o nome do seu estabelecimento. (Recurso atrás de liberação; enquanto desligado, o campo não altera a fatura.) |
| expiresIn | int? | Opcional. Segundos até expirar (60–86400). |
| method | "pix" | "card" | "boleto" | Opcional (default pix). Cartão exige o bloco card (veja “Cobrar no cartão”); boleto devolve boleto.barcode + boleto.dueDate e confirma pelo webhook. |
| card.token | string | Só method=card. Token gerado no navegador (o PAN nunca toca nosso servidor). |
| card.processor | string? | Só method=card. DEVE bater com o processor devolvido por GET /card-tokenization (o token pertence àquela processadora). Divergente → a cobrança é recusada (re-tokenize com a atual). Compatibilidade: o campo card.acquirer legado ainda é aceito na requisição. |
| card.holderPostalCode | string | OBRIGATÓRIO no cartão. CEP do titular (antifraude/AVS das adquirentes). |
| card.holderAddressNumber | string | OBRIGATÓRIO no cartão. Número do endereço do titular. |
| card.installments | int? | Só method=card. Parcelas SEM juros ao comprador (1 = à vista, default). O número MÁXIMO de parcelas vem no GET /card-tokenization (campo maxInstallments): é a capacidade DESTA conta. Pedir MAIS que o máximo NÃO é re-clampado em silêncio: a cobrança é RECUSADA com erro explícito informando o máximo daquela transação (sem cobrar). O comprador paga o total dividido por N sem juros; o custo da parcela é do lojista (taxa por parcela). |
No cartão, além do token, são obrigatórios customer.email, customer.phone (com DDD), card.holderPostalCode (CEP) e card.holderAddressNumber. Faltando qualquer um, a API responde 422 validation_error apontando o campo, antes de tocar a adquirente.
curl -X POST https://paynuvra.com/api/v1/charges \
-H "Authorization: Bearer nvr_live_SUA_CHAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{
"amount": 15000,
"customer": { "name": "Maria Silva", "document": "12345678909" },
"externalReference": "pedido-4821",
"description": "Plano Pro",
"expiresIn": 3600
}'Resposta (201):
{
"id": "ord_...",
"externalReference": "pedido-4821",
"status": "pending",
"amount": 15000,
"pix": {
"copyPaste": "00020126...5204",
"qrCodeBase64": "data:image/png;base64,...",
"expiresAt": "2026-07-08T12:00:00.000Z"
}
}status: pending · approved · expired · refunded · refused. paidAt vem quando aprovado. Acompanhe a aprovação pelo webhook order.approved (não faça polling agressivo).
Cobrar no cartão (via API)
token de uso único, e só esse token vai para a sua API. Nunca tokenize no backend, nunca guarde o token (ele é de uso único e expira). Isso mantém o seu PCI no nível mínimo (SAQ-A).Cobre no cartão pela sua própria tela (sem iframe), em 3 passos: (1) descubra o tokenizador da conta, (2) tokenize o cartão no navegador, (3) crie a cobrança com o token.
O que o cartão exige (todos obrigatórios)
As adquirentes de cartão validam o titular (antifraude/AVS), então estes campos são obrigatórios. Faltando qualquer um, a resposta é 422 validation_error apontando o campo, e nada é cobrado.
| Campo | Exemplo | Por que |
|---|---|---|
amount | 3000 | Valor em centavos (R$ 30,00). |
externalReference | pedido-77 | Sua chave de idempotência (reenviar devolve a mesma cobrança). |
customer.name | Maria Silva | Nome do titular do cartão. |
customer.document | 12345678909 | CPF ou CNPJ, só dígitos. Exigido no antifraude. |
customer.email | maria@exemplo.com | Recibo e antifraude da adquirente. |
customer.phone | +5521999998888 | Qualquer formato BR (com/sem +55, com/sem pontuação); normalizamos. Precisa ser válido com DDD, as adquirentes recusam cartão sem telefone. |
card.token | token_lgxVY49... | Token gerado no NAVEGADOR (passo 2). Nunca no backend. |
card.processor | cp_06 | Código OPACO da processadora; tem que bater com o processor devolvido por GET /card-tokenization. (card.acquirer legado ainda é aceito.) |
card.holderPostalCode | 20000000 | CEP do titular, só dígitos. AVS/antifraude. |
card.holderAddressNumber | 100 | Número do endereço do titular. AVS. |
Exemplo completo (aprovado em produção)
Passo 1. Descubra o tokenizador da conta:
/api/v1/card-tokenizationcurl https://paynuvra.com/api/v1/card-tokenization \
-H "Authorization: Bearer nvr_live_SUA_CHAVE_AQUI"
// resposta
{
"available": true,
"processor": "cp_06", // código OPACO da processadora DESTA conta (eco em card.processor)
"scheme": "s1", // formato de tokenização (ver "Passo 2"): "s1" | "s2" | "sandbox"
"publicKey": "pk_...", // chave publicável (vai ao navegador)
"tokenizeUrl": "https://...", // endpoint de tokenização (do provedor); poste o cartão AQUI
"maxInstallments": 18 // nº máximo de parcelas DESTA conta (capacidade)
}O processor é um código estável e OPACO (não revela a processadora; roteamento é interno). Chave nvr_dev_ (teste) aponta para o sandbox. Sem tokenizador disponível, available vem false.
Passo 2. Tokenize o cartão no navegador (ver os dois formatos logo abaixo) e obtenha o token.
POST /api/v1/charges em seguida. Não guarde o token nem tokenize com antecedência. Se estourar os 60s, gere um novo antes de cobrar.Passo 3. Crie a cobrança com o token (o número/validade/CVV do cartão nunca vão no corpo, a API os recusa):
/api/v1/chargescurl -X POST https://paynuvra.com/api/v1/charges \
-H "Authorization: Bearer nvr_live_SUA_CHAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{
"amount": 3000,
"method": "card",
"externalReference": "teste-cartao-006",
"customer": {
"name": "Maria Silva",
"document": "12345678909",
"email": "maria@exemplo.com",
"phone": "+5521999998888"
},
"card": {
"token": "token_lgxVY49NcNtZLPGq",
"processor": "cp_06",
"holderPostalCode": "20000000",
"holderAddressNumber": "100",
"installments": 3
}
}'card.installments é opcional (default 1 = à vista). Parcelas SEM juros ao comprador: ele paga o total dividido por N; a taxa por parcela é do lojista. O máximo é a capacidade DESTA conta, informada em maxInstallments no GET /card-tokenization. Pedir mais que o máximo não é re-clampado em silêncio: a cobrança é recusada com erro explícito informando o máximo daquela transação (sem cobrar).
Resposta de sucesso (aprovado):
{
"id": "ord_3Kk9x2",
"externalReference": "teste-cartao-006",
"status": "approved",
"amount": 3000, // bruto (centavos)
"fee": 169, // taxa
"net": 2831, // líquido que entra no seu saldo
"currency": "BRL",
"method": "card",
"customer": { "name": "Maria Silva", "document": "12345678909", "email": "maria@exemplo.com" },
"paidAt": "2026-08-20T21:03:00.000Z",
"createdAt": "2026-08-20T21:02:58.000Z"
}O status já pode vir approved ou refused; confirme sempre pelo webhook order.approved.
Passo 2 em detalhe: os formatos de tokenização
Siga sempre o que o /card-tokenization devolveu para a conta. O campo scheme diz o FORMATO da requisição; poste SEMPRE na tokenizeUrl devolvida (é o endpoint do provedor de tokenização). Se o processor/scheme da conta mudar, o token antigo não vale mais: re-tokenize com o atual. Os formatos diferem:
scheme: s1
POST <tokenizeUrl devolvida pelo /card-tokenization>
Content-Type: application/json
{
"type": "card",
"card": {
"number": "4111111111111111",
"holder_name": "Maria Silva",
"exp_month": 12, // INTEIRO (1 a 12)
"exp_year": 2028, // AAAA (4 dígitos)
"cvv": "123"
}
}
// resposta: { "id": "token_...", ... }
// ATENÇÃO: o token EXPIRA EM ~60 SEGUNDOS. Tokenize e cobre em seguida.scheme: s2
POST <tokenizeUrl devolvida pelo /card-tokenization>
Content-Type: application/json
{
"publishableKey": "<publicKey>", // vai no CORPO
"card": {
"number": "4111111111111111",
"expMonth": "12", // MM (2 dígitos, string)
"expYear": "28", // AA (2 dígitos, string)
"cvv": "123",
"holderName": "Maria Silva"
}
}
// resposta: { "token": "token_...", ... }scheme: sandbox
A chave de teste (nvr_dev_) devolve scheme: "sandbox" e não tem endpoint de tokenização (vem sem tokenizeUrl). Nenhuma processadora real é tocada. Você monta o token localmente, sem chamar nada: sbxtok_ seguido dos dígitos do cartão de teste. A decisão é determinística pelo último dígito: PAR aprova, ÍMPAR recusa.
// GET /api/v1/card-tokenization com chave de teste (nvr_dev_) → SEMPRE sandbox:
{
"available": true,
"processor": "cp_sandbox",
"scheme": "sandbox",
"publicKey": "pk_sandbox_dev",
"maxInstallments": 1
// NÃO vem "tokenizeUrl": o sandbox não tem endpoint de tokenização (a publicKey não é usada)
}
// Monte o token LOCALMENTE (não chame nenhum endpoint):
// token = "sbxtok_" + <dígitos do cartão de teste>
// Último dígito decide (determinístico):
"sbxtok_4111111111111112" // termina em 2 (PAR) → APROVA
"sbxtok_4111111111111111" // termina em 1 (ÍMPAR) → RECUSACobrando com o token de sandbox (mesmos campos obrigatórios de cartão):
curl -X POST https://paynuvra.com/api/v1/charges \
-H "Authorization: Bearer nvr_dev_SUA_CHAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{
"amount": 3000,
"method": "card",
"externalReference": "teste-sandbox-001",
"customer": {
"name": "Maria Silva",
"document": "12345678909",
"email": "maria@exemplo.com",
"phone": "+5521999998888" // telefone do portador: OBRIGATÓRIO no cartão
},
"card": {
"token": "sbxtok_4111111111111112", // termina em PAR → aprova
"processor": "cp_sandbox", // tem que bater com o processor do GET
"holderPostalCode": "20000000", // CEP do portador: OBRIGATÓRIO
"holderAddressNumber": "100" // número do endereço: OBRIGATÓRIO
}
}'Cartão via API (sandbox ou produção) exige a flag global API_CARD_CHARGES_ENABLED: enquanto ela estiver desligada, tanto o GET /card-tokenization quanto o POST /charges de cartão respondem 503 (card_charges_disabled). O sandbox usa a chave nvr_dev_ (homologação, sem dinheiro real).
Erros comuns (causa e correção)
| Erro | Causa | Correção |
|---|---|---|
422 validation_error | Faltou um campo obrigatório do titular (o corpo aponta qual em details.fieldErrors). | Envie o campo indicado. Nada foi cobrado. |
409 idempotency_error | A externalReference já foi usada com um payload diferente (outro valor/método/documento/e-mail). | Use uma referência nova para uma cobrança nova; para repetir, mande o payload idêntico. |
402 card_charge_failed | A adquirente recusou o cartão (saldo/limite/antifraude). Nada é debitado. | Tente outro cartão. O motivo cru fica no painel (transação, ícone de detalhe). |
Token expirado | O token dura ~60s; demorou entre tokenizar e cobrar. | Tokenize e chame POST /charges em seguida. Gere um token novo se expirar. |
Token de outra processadora | O token pertence ao processor do /card-tokenization; a configuração da conta mudou. | Re-tokenize (confira processor no /card-tokenization). |
Corpo de um 422 (campo faltando):
{
"error": "validation_error",
"details": {
"formErrors": [],
"fieldErrors": {
"card": ["Para cartão, informe o CEP do titular (card.holderPostalCode)."]
}
}
}Requer acesso de produção com o método Cartão liberado (Desenvolvedor, Acesso de produção). Para autenticação 3DS (quando ligada para o seu estabelecimento), veja a seção 3DS abaixo.
3DS (autenticação do cartão)
O 3D Secure adiciona a autenticação do banco do portador ao cartão (liability shift). É opcional e ligado por estabelecimento: com 3DS desligado, o cartão via API segue igual. Com 3DS ligado, a cobrança de cartão exige a autenticação - feita no navegador pelo SDK 3DS da processadora. O PAN não passa pelo seu servidor nem pelo nosso. (O scriptUrl é o SDK do provedor de 3DS; a URL é dele.)
1. Peça o token 3DS (server-side, sua chave):
/api/v1/3ds/tokencurl -X POST https://paynuvra.com/api/v1/3ds/token \
-H "Authorization: Bearer nvr_live_SUA_CHAVE_AQUI"
# → { "token": "<token 3DS>", "scriptUrl": "<URL do SDK 3DS do provedor>", "env": "prod" }2. No navegador: carregue o scriptUrl e rode TDS.init com os dados do cartão:
const r = await window.TDS.init(
{ token, tds_method_container_element: elA, challenge_container_element: elB, use_default_challenge_iframe_style: true, challenge_window_size: "03" },
{ payments: [{ payment_method: "credit_card", credit_card: { card: { number, holder_name, exp_month, exp_year, billing_address } }, amount }], customer, items }
);
// r[0] = { tds_server_trans_id, trans_status, authenticated_card, challenge_canceled }
// O SDK renderiza o desafio (iframe) sozinho quando o banco exige. Frictionless: retorna direto.3. Envie a cobrança com o resultado da autenticação:
curl -X POST https://paynuvra.com/api/v1/charges \
-H "Authorization: Bearer nvr_live_SUA_CHAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{
"method": "card",
"amount": 20000,
"customer": { "name": "João Silva", "document": "12345678901", "email": "joao@ex.com", "phone": "+5511999998888" },
"card": {
"token": "<card_token da tokenização>",
"holderPostalCode": "01234567", "holderAddressNumber": "123",
"authentication": { "transactionId": "<tds_server_trans_id>", "transStatus": "Y" }
},
"externalReference": "pedido-3ds-1"
}'trans_status: Y autenticado · A tentado (ambos seguem, com liability shift) · N não autenticado · R negado · U indisponível · C desafio não concluído · I informativo. Com 3DS ligado, só Y/A prosseguem; os demais (ou ausência da authentication) resultam em recusa honesta - nada é cobrado. A aprovação final é sempre confirmada por reconsulta/webhook(charge.paid), nunca pelo retorno da tela.
Pré-requisito: 3DS habilitado na processadora (Visa/Mastercard, 3DS 2.x) e ligado pela plataforma para o seu estabelecimento. O antifraude/AVS de hoje (documento, e-mail, telefone, CEP e número) continua valendo junto com o 3DS.
Cobrança via Boleto
/api/v1/chargesMesmo endpoint da cobrança Pix, com "method": "boleto". A resposta traz a linha digitável (boleto.barcode) e o vencimento (boleto.dueDate). Requer acesso de produção com o método Boleto liberado e uma adquirente de boleto ativa na conta.
curl -X POST https://paynuvra.com/api/v1/charges \
-H "Authorization: Bearer nvr_live_SUA_CHAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{
"method": "boleto",
"amount": 15000,
"customer": { "name": "Maria Silva", "document": "12345678909", "email": "maria@ex.com" },
"externalReference": "pedido-4471"
}'{
"id": "ord_...",
"method": "boleto",
"status": "pending",
"amount": 15000,
"boleto": {
"barcode": "34191.79001 01043.510047 91020.150008 9 96660000019790",
"dueDate": "2026-09-03T00:00:00.000Z"
}
}O crédito nunca vem só do webhook: o pagamento é confirmado pelo evento charge.paid seguido de reconsulta na adquirente (fonte da verdade). Liquida em D+2 (dias úteis). Consulte o status por GET /api/v1/charges/{id} como fallback.
Consultar cobrança
/api/v1/charges/{id}Devolve o mesmo objeto da criação, com o status atual (útil como fallback ao webhook).
curl https://paynuvra.com/api/v1/charges/ord_... \ -H "Authorization: Bearer nvr_live_SUA_CHAVE_AQUI"
Checkout embedded (cartão)
O comprador paga o cartão sem sair da sua tela: você cria a sessão pela API, recebe uma embed_url e embute nosso checkout num iframe. A tokenização e o antifraude (validação de titular/AVS) são nossos: você não toca em dado de cartão (PCI mínimo). Quando o 3DS está ligado para o seu estabelecimento, a autenticação do banco também entra no fluxo (ver a seção 3DS).
1. Crie a sessão (server-side, com sua chave nvr_live_):
/api/v1/checkout-sessionscurl -X POST https://paynuvra.com/api/v1/checkout-sessions \
-H "Authorization: Bearer nvr_live_SUA_CHAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{ "amount": 3490, "description": "Recuperacao carrinho #8842" }'{
"id": "COB-8F3K2A",
"object": "checkout_session",
"method": "card",
"amount": 3490,
"embed_url": "https://paynuvra.com/embed/eyJ...<token>",
"expires_at": "2026-08-11T23:59:00.000Z",
"allowed_origins": ["https://sualoja.com"]
}| Campo | Tipo | Descrição |
|---|---|---|
| amount | int (centavos) | Valor da cobrança. Definido no servidor, o comprador NÃO altera. Obrigatório. |
| description | string? | Aparece no checkout. Opcional (máx. 200). |
| externalReference | string? | Sua referência (idempotência/conciliação). Opcional (máx. 128). |
Erros deste endpoint:
| Campo | Tipo | Descrição |
|---|---|---|
| 400 | live_key_required | Sessão embedded exige chave de produção (nvr_live_); sandbox não cria sessão. |
| 403 | method_not_granted | O método cartão não está liberado no seu acesso de produção. |
| 403 | account_not_cleared | Conta sem KYC aprovado / e-mail não confirmado. |
| 422 | embed_not_configured | Nenhum domínio de embed cadastrado (Desenvolvedor → Domínios de embed). |
| 422 | validation_error | Campos inválidos (veja details). |
| 503 | function_unavailable | Cobrança de cartão bloqueada para a conta no momento. |
| 502 | session_failed | Falha ao criar a sessão. Tente novamente. |
2. Embute no seu site (uma linha de SDK):
<script src="https://paynuvra.com/embed.js"></script>
<div id="pay"></div>
<script>
PayEmbed.checkout({
url: EMBED_URL, // a embed_url da sessão
mount: "#pay",
onPaid: function(e){ window.location = "/obrigado"; },
onRefused: function(e){ /* mostrar erro, permitir tentar de novo */ },
onClose: function(e){ /* comprador fechou */ }
});
</script>Eventos recebidos na sua página: onReady, onResize (altura automática), onProcessing, onPaid, onRefused, onPending (análise da adquirente) e onClose. O webhook charge.paid continua sendo a confirmação oficial (o evento na tela é pra UX).
Segurança: só os domínios cadastrados na sua conta (Desenvolvedor → Domínios de embed) podem embutir o checkout; a sessão expira; e o valor vem sempre da cobrança criada por API.
Saldo
/api/v1/balanceSaldos por método em centavos: disponível, pendente (a liquidar) e reservado (retido por disputa/MED).
{
"environment": "live",
"balances": [
{ "method": "pix", "available": 124050, "pending": 0, "reserved": 0 }
]
}Internacional - câmbio e recebimento
Requer o método internacional liberado no acesso de produção (Desenvolvedor → Acesso de produção) e a habilitação da conta pela plataforma. Sem isso: 403 method_not_granted. Valores em unidades menores (centavos p/ BRL/MXN).
Câmbio (2 passos: cotar → converter)
/api/v1/fx/quoteCota BRL→MXN ou BRL→USDT (a cotação é assinada e expira).
curl -X POST https://paynuvra.com/api/v1/fx/quote \
-H "Authorization: Bearer nvr_live_..." -H "Content-Type: application/json" \
-d '{ "from": "BRL", "to": "MXN", "amountCents": 100000 }'
// → { "quoteId": "...", "rate": ..., "estimatedAmountCents": ..., "feeCents": ..., "expiresAt": "..." }/api/v1/fx/convertExecuta a cotação DENTRO da validade. Idempotente pela cotação (converte 1×).
-d '{ "quoteId": "...", "idempotencyKey": "op-123" }'
// 409 quote_expired (fora da validade) · 503 liquidity_unavailable (sem float; nada debitado)Mínimo de conversão: R$ 50,00 (padrão; abaixo disso a taxa fixa do câmbio fica alta demais). Abaixo → recusa com o valor mínimo na mensagem.
Recebimento internacional
/api/v1/foreign/cash-inGera a instrução de recebimento (CLABE/SPEI, MXN). O crédito é confirmado por webhook + reconsulta - nunca pelo corpo da resposta.
-d '{ "currency": "MXN", "amount": 50000 }' // amount em unidades menores (centavos MXN)
// → { "id": "...", "clabe": "...", "status": "awaiting", ... }/api/v1/foreign/cash-in/{id}Status do recebimento: awaiting → credited (fonte da verdade: reconsulta no webhook).
Mínimo de recebimento: 20,00 MXN (piso da adquirente para SPEI/CLABE). Abaixo → 422 below_minimum.
Pagamento internacional (saída)
/api/v1/foreign/payoutEnvia moeda para fora: MXN via CLABE/SPEI (clabe+name+document) ou USDT on-chain TRON (address). O destinatário é validado ANTES de qualquer débito (rede/CLABE erradas = perda irreversível). Idempotente por externalReference. Requer o método internacional + permissão payout:create.
-d '{ "currency": "MXN", "amount": 50000, "externalReference": "pay-1",
"clabe": "0123...(18)", "name": "Fulano", "document": "..." }'
// USDT: { "currency": "USDT", "amount": 100000000, "externalReference": "pay-2", "address": "T..." }
// → { "id": "...", "status": "processing", "currency": "MXN", "amount": 50000 }
// 422 insufficient_funds (saldo do cofre) · 422 invalid_recipient · 502 foreign_payout_failed (nada debitado)/api/v1/foreign/payout/{id}Estado público: processing → completed | failed. A liquidação (e o estorno da reserva em falha) fecha SÓ por webhook + reconsulta da mesa - nunca pelo corpo. Enquanto as flags de saída não ligarem, responde 503 foreign_payout_disabled.
Moedas: câmbio BRL↔MXN/USDT; recebimento MXN; saída MXN (CLABE/SPEI) e USDT (TRON). Outras não são suportadas pela adquirente internacional hoje. Valores em unidades menores (MXN scale 2; USDT scale 6).
Cripto - compra e saque
Requer o método cripto liberado no acesso de produção. Fluxo em 2 passos (cotar → executar). Valores em centavos. O envio on-chain é IRREVERSÍVEL: a rede e o endereço são validados e travados na cotação.
/api/v1/crypto/quoteCota compra (operation:"buy") ou saque (operation:"withdraw"). O endereço/rede entram AQUI (travados na cotação).
curl -X POST https://paynuvra.com/api/v1/crypto/quote \
-H "Authorization: Bearer nvr_live_..." -H "Content-Type: application/json" \
-d '{ "operation": "buy", "asset": "USDT", "amount": 50000, "address": "0x...", "network": "polygon" }'
// → { "quoteId": "...", "price": "...", "cryptoAmount": "...", "spread": <centavos>, "expiresAt": "...", "irreversible": true }/api/v1/crypto/buyExecuta a compra da cotação (dentro da validade). Idempotente pela cotação.
-d '{ "quoteId": "...", "idempotencyKey": "op-1" }' // → { "operationId": "...", "state": "settling", "cryptoAmount": "..." }/api/v1/crypto/withdrawSaca o saldo em cripto. Se enviar address/network, DEVEM bater com os da cotação (anti-swap) - senão 422 address_mismatch.
-d '{ "quoteId": "...", "address": "0x...", "network": "polygon", "idempotencyKey": "op-2" }'
// 409 quote_expired (fora da validade) · 502 crypto_settlement_failed (falha na mesa; NADA debitado, estorno net-zero)Limites (compra e saque):
| Campo | Tipo | Descrição |
|---|---|---|
| Mínimo por ordem | R$ 300,00 (para todos) | Regra de margem da plataforma (a mesa cobra a partir de R$ 150; os R$ 300 são a margem Nuvra). NÃO é por estabelecimento. Abaixo → 422 below_min ("O valor mínimo para compra/saque de cripto é R$ …"). |
| Máximo por operação | R$ 5.000,00 (padrão) | Acima → 422 over_tx ("Valor acima do máximo por operação (R$ …)."). Definido pela plataforma (env). |
| Teto diário | R$ 20.000,00 (padrão) | Soma de compras/saques do dia. Acima → 422 over_day ("Limite diário … atingido."). Verificado atomicamente no confirm. |
Venda de cripto (off-ramp) - ASSÍNCRONA:
/api/v1/crypto/sellCria a ordem de venda. Devolve o depositAddress da mesa: o cliente envia a cripto para lá. O depósito on-chain é IRREVERSÍVEL.
-d '{ "asset": "USDT", "amount": "100", "walletAddress": "0x...", "network": "polygon" }'
// → {
// "id": "...", "side": "sell", "depositAddress": "0x...", "status": "awaiting_deposit", "network": "polygon",
// "estimatedBrl": { // PREVISÃO do que entra no seu saldo
// "estimated": true, "locked": false, // referência do momento da criação - NÃO travado
// "quotedAt": "2026-09-01T...Z",
// "grossBrlCents": 60000, // bruto estimado (cotação da mesa)
// "feeCents": 900, "netCents": 59100, // taxa/spread + líquido estimado
// "note": "Estimativa (...). O valor final é o do momento da liquidação."
// }
// }estimatedBrl é ESTIMATIVA, não valor travado. A venda é assíncrona: o depósito on-chain chega depois, então o preço final é o do momento da liquidação. Quando a ordem liquida, o GET traz o valor CONFIRMADO (estimated: false, locked: true) e os campos grossBrlCents/feeCents/netCents no topo.
/api/v1/crypto/sell/{id}/confirmInforma a transactionHash do depósito on-chain. NÃO credita - o crédito em BRL fecha só pela fonte da verdade (webhook da mesa + reconsulta).
-d '{ "transactionHash": "0x..." }' // → { "id": "...", "status": "processing" }/api/v1/crypto/sell/{id}Estado público da ordem: awaiting_deposit → processing → completed (com grossBrlCents/feeCents/netCents) · failed · cancelled (expira em 24h sem depósito). Acompanhe por polling neste GET; o crédito aparece no seu saldo quando a mesa confirma o recebimento (webhook interno).
Depósito a MENOR ou a MAIOR (o valor informado é só referência da cotação):
O crédito é sempre proporcional ao que REALMENTE chegou - a mesa converte a cripto efetivamente depositada e nós creditamos esse BRL menos a taxa. Oamount informado na criação NÃO trava nada; serve só para a cotação de referência.
- Depositou MENOS: credita o líquido do valor menor que chegou. Se o BRL recebido ficar abaixo de R$ 1,00, a ordem vai a
failede NADA é creditado (não fica pendente esperando o resto). - Depositou MAIS: credita o líquido do valor maior - sem teto. Você recebe pelo que efetivamente enviou.
Nunca há crédito parcial "aguardando o resto": cada depósito é conciliado pelo que a mesa confirmou. A previsão em BRL (estimatedBrl) é do valor informado; o valor final é o do depósito real na liquidação.
Redes suportadas (compra, saque e venda) - validadas ANTES de qualquer débito:
| Campo | Tipo | Descrição |
|---|---|---|
| polygon | EVM - 0x + 40 hex | Endereço fora do formato → recusado no quote (nunca debita). |
| ethereum | EVM - 0x + 40 hex | Mesmo formato do Polygon: escolha a rede CERTA - endereço EVM não distingue a chain (ver aviso). |
| tron | T + 33 (base58) | USDT-TRC20 típico. |
| solana | base58, 32–44 | — |
Rede errada = perda irreversível. Validamos o FORMATO por rede antes do débito (rede fora da lista ou formato inválido → recusa). Mas endereços EVM (polygon/ethereum) têm o MESMO formato - se você escolher a chain errada com um endereço EVM válido, o formato passa e a mesa faz a 2ª checagem; confira sempre a rede. O par ativo × rede (ex.: USDT) é definido e validado pela MESA na cotação - não é lista fixa nossa; par não suportado → 422 unsupported. A VENDA nasce indisponível até as flags ligarem (503 crypto_sell_disabled). (A mesa é uma instituição parceira; o roteamento é interno.)
Pix Automático (cobrança recorrente)
Requer o método Pix Automático (recorrente) liberado no acesso de produção. O pagador AUTORIZA a recorrência uma vez (paga o QR da 1ª cobrança) e as próximas são debitadas automaticamente. Valores em centavos. Idempotente por externalReference.
/api/v1/recurring/authorizationsCria a autorização + o plano. Valor fixo (amount) OU variável (minAmount = piso), nunca os dois. A resposta traz o consent (QR copia-e-cola + imagem) que o pagador paga para autorizar - a assinatura nasce awaiting_authorization e vira active quando a autorização é confirmada.
curl -X POST https://paynuvra.com/api/v1/recurring/authorizations \
-H "Authorization: Bearer nvr_live_..." -H "Content-Type: application/json" \
-d '{
"externalReference": "assinatura-123",
"description": "Plano mensal",
"amount": 5000,
"frequency": "MONTHLY",
"startDate": "2026-09-10",
"retryPolicy": "three_in_seven",
"customer": { "name": "Fulano", "document": "12345678909", "email": "f@ex.com" }
}'
// → { "id": "...", "status": "awaiting_authorization", "consent": { "pixCopyPaste": "...", "qrCodeBase64": "..." },
// "frequency": "MONTHLY", "nextChargeAt": "...", "amount": 5000 }| Campo | Tipo | Descrição |
|---|---|---|
| externalReference | string | Sua referência única (idempotência). Repetir devolve a mesma assinatura. |
| amount / minAmount | integer | Valor fixo por ciclo (amount) OU piso p/ valor variável (minAmount). Centavos. Exatamente um. |
| frequency | string | WEEKLY | MONTHLY | QUARTERLY | SEMIANNUALLY | ANNUALLY. |
| startDate / finishDate | date | Início e teto temporal opcional (YYYY-MM-DD). |
| retryPolicy | string | none (sem retentativa) | three_in_seven (até 3 em 7 dias corridos por saldo insuficiente). |
| customer | object | name, document (CPF/CNPJ), email?, phone? do pagador. |
/api/v1/recurring/authorizations/{id}Status da assinatura: awaiting_authorization · active · failing (um ciclo falhou por saldo; a autorização segue ativa) · cancelled_by_seller · cancelled_by_payer · refused · expired.
/api/v1/recurring/authorizations/{id}/chargesHistórico dos ciclos: cada um com status (scheduled | paid | refused | cancelled), amount, dueDate, paidAt.
/api/v1/recurring/authorizations/{id}Cancela a autorização (encerra os agendamentos pendentes). Idempotente. O pagador também pode cancelar pelo banco dele - refletimos como cancelled_by_payer.
Cada ciclo pago é uma VENDA (entra no seu saldo com a taxa Pix, liquidação normal) e dispara os mesmos webhooks de cobrança (order.approved). Falha por saldo insuficiente NÃO cancela a assinatura - o próximo ciclo segue.
Enquanto o motor de recorrência não está ligado, a rota responde 503 recurring_disabled (honesto, nunca cria pela metade). Contrato via instituição parceira (Jornada 3).
Pedidos
/api/v1/ordersOs últimos 50 pedidos da conta (id, cliente, valor em centavos, status, método, data).
Payout (saque via API)
/api/v1/payoutsRequer uma chave com permissão de payout (sem ela → 403 forbidden_scope). Pode exigir IP na allowlist. Recebedoras precisam de KYC aprovado: pendente retorna 403 kyc_required. Idempotente por externalReference.
| Campo | Tipo | Descrição |
|---|---|---|
| amount | int (centavos) | Valor que o DESTINATÁRIO recebe. A taxa é debitada por cima. |
| pixKey | string | Chave Pix do destinatário. |
| pixKeyType | "cpf"|"cnpj"|"email"|"phone"|"random" | Tipo da chave (validado). |
| externalReference | string? | Sua chave de idempotência. |
| description | string? | Opcional. |
curl -X POST https://paynuvra.com/api/v1/payouts \
-H "Authorization: Bearer nvr_live_SUA_CHAVE_DE_PAYOUT" \
-H "Content-Type: application/json" \
-d '{
"amount": 50000,
"pixKey": "12345678909",
"pixKeyType": "cpf",
"externalReference": "saque-1099"
}'Resposta: { id, status, amount (líquido), fee, totalDebited, ... }. status: processing · completed · failed.
Limite diário. Existe um teto de saque por dia (fuso America/Sao_Paulo). O painel e a API somam no MESMO balde: um saque feito pelo painel consome o mesmo limite de um saque por API. Excedido → resposta 422 com code: "daily_limit_exceeded" e mensagem informando o teto do dia e o quanto ainda resta. Nada é debitado.
Consultar saque
/api/v1/payouts/{id}Requer a mesma chave com permissão de payout. Devolve o saque com o status atual: use como fallback ao webhook payout.completed / payout.failed.
curl https://paynuvra.com/api/v1/payouts/pay_... \ -H "Authorization: Bearer nvr_live_SUA_CHAVE_DE_PAYOUT"
Pagar boleto (bill_pay)
/api/v1/bill-paymentsPaga um boleto de terceiro a partir do seu saldo. Dinheiro saindo: exige a permissão sensível bill_pay:create (opt-in) e o mesmo IP allowlist do saque. O valor é sempre o do código de barras(o amountCents só é usado quando o boleto não embute valor). Boleto vencido é recusado. Idempotente por externalReference.
curl -X POST https://paynuvra.com/api/v1/bill-payments \
-H "Authorization: Bearer nvr_live_SUA_CHAVE_COM_BILL_PAY" \
-H "Content-Type: application/json" \
-d '{
"identificationField": "34191.79001 01043.510047 91020.150008 9 96660000019790",
"externalReference": "conta-luz-08-2026"
}'Resposta: { id, status, amount, beneficiary, dueDate, externalReference, createdAt }. status: processing · paid · failed.
/api/v1/bill-payments/{id}A confirmação é sempre pela fonte da verdade (reconsulta na fonte da verdade via webhook bill_payment.paid / bill_payment.failed) - nunca só pela resposta imediata. Falha na adquirente estorna o débito automaticamente.
Webhooks de saída
Cadastre a URL em Desenvolvedor → Webhooks e escolha os eventos. Eventos disponíveis:
order.createdorder.approvedorder.refusedorder.refundedcharge.createdcharge.paidcharge.failedcharge.refundedcharge.disputedcart.abandonedtransfer.completedpayout.requestedpayout.completedpayout.failedsubscription.activatedsubscription.cancelled_by_payersubscription.refusedrecurring_charge.refusedPagamento aprovado - quais eventos chegam? Todo pagamento aprovado (checkout, link de cobrança e API) dispara os dois: order.approved e seu apelido público charge.paid - com o mesmo objeto data. Assine apenas um deles (ou trate os dois como o mesmo evento, deduplicando por data.id) para não processar a venda em dobro. O mesmo vale para os pares order.refused/charge.failed e order.refunded/charge.refunded. charge.created acompanha order.created (cobrança gerada). Além desses, charge.disputed chega quando uma venda vira disputa/chargeback e transfer.completed quando uma transferência interna é concluída.
Pix Automático (recorrente). Cada ciclo cobrado é uma venda: quando liquida, chega order.approved/charge.paid como qualquer cobrança (mesmo formato). Consulte o estado da assinatura e o histórico de ciclos pelas rotas /api/v1/recurring/authorizations/{id} e /charges. Falha de um ciclo por saldo insuficiente vira order.refused e a assinatura fica failing, mas a autorização permanece ativa para o próximo ciclo (não é cancelada).
Eventos do ciclo de vida da assinatura. Além dos de cobrança, você pode assinar: subscription.activated (o pagador autorizou a recorrência), subscription.cancelled_by_payer (cancelou pelo banco dele), subscription.refused (não autorizada) e recurring_charge.refused (um ciclo falhou por saldo insuficiente/sem limite). O data traz id/externalReference da assinatura (e cycleReference/amount no ciclo recusado).
O corpo é { event, data, sentAt }. Para eventos order.* (e seus apelidos charge.*), o data traz o pedido (o valor vem em total, com amountCents como apelido do mesmo valor):
{
"event": "order.approved",
"data": {
"id": "ord_...",
"status": "approved",
"method": "pix",
"total": 15000,
"customer": {
"name": "Maria Silva",
"email": "maria@example.com",
"phone": "+5511999998888" // E.164 quando o comprador informou; null se não
},
"createdAt": "2026-07-08T11:00:00.000Z"
},
"sentAt": "2026-07-08T11:00:01.000Z"
}O customer.phone é o telefone do comprador em E.164 (+55…) quando informado, ou null. Ele aparece em todos os order.*/charge.* e no cart.abandoned (carrinho pendente não pago dentro da janela): use-o para recuperar a venda por WhatsApp. Sem telefone informado, o evento chega sem telefone.
Cada entrega traz os headers X-Nuvra-Event (tipo), X-Nuvra-Event-Id (id ÚNICO da entrega) e X-Nuvra-Signature: um HMAC-SHA256 do corpo cru usando o segredo do webhook. Deduplique pelo X-Nuvra-Event-Id, uma reentrega carrega o MESMO id. Contra replay, confira também o sentAt do corpo (está assinado): rejeite entregas fora de uma janela (ex.: ±5 min). O histórico fica em Desenvolvedor → Webhooks. Recalcule e compare a assinatura para validar a origem:
// Node.js: validação da assinatura
import { createHmac } from "node:crypto";
const expected = createHmac("sha256", WEBHOOK_SECRET)
.update(rawBody) // corpo CRU (string), antes de JSON.parse
.digest("hex");
if (expected !== req.headers["x-nuvra-signature"]) {
return res.status(401).end(); // origem não confiável
}
const { event, data, sentAt } = JSON.parse(rawBody);Erros
Erros vêm como { error, message?, details? } com o código HTTP:
| Campo | Tipo | Descrição |
|---|---|---|
| 401 | unauthorized | Chave ausente, inválida ou revogada. |
| 403 | insufficient_scope | A chave não tem a permissão exigida (ex.: charge:create). O campo required indica qual falta. |
| 403 | forbidden_scope / ip_not_allowed / kyc_required | Sem permissão de payout, IP não autorizado, ou KYC pendente. |
| 403 | api_disabled | API da conta desabilitada pela plataforma. |
| 422 | validation_error | Campos inválidos (veja details). |
| 422 | daily_limit_exceeded | Teto diário de saque atingido (painel + API somam no mesmo balde, fuso America/Sao_Paulo). A message traz o teto do dia e o disponível restante. Nada foi debitado. |
| 422 | refund_not_allowed / simulate_not_allowed | Estorno só de cobrança paga; simulação só no sandbox em teste. |
| 402 | card_charge_failed | Cartão recusado/indisponível (nada foi debitado). Veja message/fieldErrors. |
| 403 | method_not_granted | O método não está liberado no seu acesso de produção. Métodos: Pix, cartão, boleto, internacional (câmbio + recebimento) e cripto (compra/saque). Solicite a liberação do método em Desenvolvedor → Acesso de produção. |
| 403 | method_unavailable | O método ainda não tem rota pública executando. Aparece na seleção como indisponível; nunca é concedido nem cobrado. (Hoje todos os métodos publicados executam.) |
| 503 | card_charges_disabled / method_unavailable | Cobrança no cartão via API ainda não habilitada, ou sem adquirente ativa para o método. |
| 502 | refund_failed | A adquirente recusou o estorno (nada foi debitado do seu saldo). |
| 429 | rate_limited | Muitas requisições. Respeite o header Retry-After (segundos) antes de repetir. Limites: charges 10/s (rajada 20), payouts 2/s (rajada 5), leituras 10/s. |
| 400 | invalid_json | Corpo não é JSON válido. |
| 502 | charge_failed / payout_failed | Falha ao processar no adquirente. |
UTMify (rastreamento de conversão)
Ao conectar sua conta UTMify, toda venda aprovada é enviada automaticamente para a UTMify (server-to-server), com as UTMs capturadas no checkout. Você não precisa programar nada; só colar o token.
Como conectar
- Entre no painel da UTMify com a sua conta.
- Abra Integrações → Credenciais de API.
- Clique em Adicionar Credencial e dê um nome.
- Copie o token (ele só aparece uma vez).
- Cole em Integrações → UTMify no painel e salve.
Use Credenciais de API, não Webhooks; são coisas diferentes e o token de Webhooks não funciona aqui.
Quando enviamos
Na aprovação da venda (o pagamento confirmado). Também refletimos reembolso quando ocorre. Nada é enviado enquanto a venda está pendente.
O que enviamos
| Campo | Tipo | Descrição |
|---|---|---|
| orderId | string | Identificador do pedido na Nuvra. |
| platform | string | Origem do evento (OneAOne). |
| paymentMethod | string | pix, credit_card ou boleto. |
| status | string | waiting_payment, paid, refused ou refunded. |
| createdAt / approvedDate / refundedAt | datetime | Datas do pedido, aprovação e reembolso (UTC). |
| customer.name / email / phone / document | string | Dados do comprador (o que ele informou no checkout). |
| customer.country / ip | string | País (BR) e IP do comprador na compra. |
| products[].id / name / planId / planName / quantity / priceInCents | array | Itens da venda e valores em centavos. |
| trackingParameters.utm_source / utm_medium / utm_campaign / utm_content / utm_term | string | UTMs capturadas no checkout. |
| trackingParameters.src / sck | string | Parâmetros extras de rastreio (quando presentes). |
| commission.totalPriceInCents / gatewayFeeInCents / userCommissionInCents / currency | object | Valores da venda em centavos e a moeda (BRL). |
Não apareceu lá?
- Confirme o estado em Integrações → UTMify: precisa estar Conectado (verde). Se estiver "Falta conectar", cole o token.
- Veja as últimas tentativas na mesma tela: o motivo aparece em português (token inválido, dado faltando).
- Token inválido: gere um novo em Credenciais de API (não em Webhooks) e cole de novo.
- Só contam vendas aprovadas depois de você conectar; vendas anteriores não são reenviadas.
Limitações e disponibilidade
Para evitar surpresa de integração, o que ainda não está disponível hoje:
- Estorno via API:
POST /api/v1/charges/{id}/refundestorna uma cobrança paga (permissãocharge:refund, opt-in; idempotente). Cancelar cobrança pendente segue no painel. - Sandbox, simular pagamento: em teste (chave
nvr_dev_),POST /api/v1/charges/{id}/simulate-approvalaprova a cobrança e dispara o webhookorder.approved, pra você exercitar o fluxo charge→webhook sem pagar de verdade. - Listagem de payouts não é exposta; consulte um saque por ID (
/api/v1/payouts/{id}). GET /api/v1/ordersé paginado por cursor:?limit=(1–100, default 50) e?cursor=(onextCursorda página anterior;null= acabou).
Entrega de webhook: até 3 tentativas com backoff na hora + reentrega por cron (outbox durável), timeout de 8s. O corpo traz event, data e sentAt; a resposta de venda/o webhook trazem fee, net e endToEndId (e2e) para reconciliação. Trate os eventos de forma idempotente, deduplique pelo header X-Nuvra-Event-Id. Se um evento falhar, reconsulte o recurso pelo GET correspondente.