API UranoPag
Cobranças PIX, saldo, saques e webhooks assinados. Endereço base https://api.uranopag.app/v1
Visão geral
A API UranoPag permite gerar cobranças PIX, consultar vendas, acompanhar o saldo e solicitar saques PIX a partir do seu sistema. Todas as requisições e respostas usam JSON (UTF-8) sobre HTTPS.
| Item | Valor |
|---|---|
| Endereço base | https://api.uranopag.app/v1 |
| Protocolo | REST + JSON |
| Ambiente | Somente produção. Não existe sandbox nem chave de teste: toda cobrança é real. |
| Valores | Sempre inteiros em centavos (1000 = R$ 10,00) |
| Datas | ISO 8601 em UTC |
Autenticação
Crie sua chave no painel em Desenvolvedores → API e Webhooks → API Keys. A chave completa aparece uma única vez; guarde-a em um cofre de segredos. Ela começa com up_live_.
Authorization: Bearer up_live_SUA_CHAVE
Content-Type: application/json- Use a chave somente no seu servidor. Nunca no navegador ou aplicativo.
- Você pode restringir a chave a IPs ou faixas (CIDR). Requisições de outros IPs recebem 403 ip_not_allowed.
- Revogue a chave no painel se suspeitar de vazamento; o efeito é imediato.
Idempotência
Criação de cobrança e de saque exigem o cabeçalho Idempotency-Key, de 16 a 128 caracteres (letras, números e hífen; use um UUID). Repetir a mesma requisição com a mesma chave devolve o mesmo recurso, sem criar outro. Repetir a chave com dados diferentes devolve 409 idempotency_mismatch.
Idempotency-Key: 2f1c9a7e-5b8d-4c3a-9e21-7d6f0b4a1c55Isso garante que cliques repetidos, novas tentativas de rede ou reenvios nunca gerem uma cobrança ou um saque duplicado.
Criar cobrança PIX
POST /v1/charges| Campo | Tipo | Obrigatório | Regra |
|---|---|---|---|
| amount_cents | inteiro | sim | Mínimo 100 (R$ 1,00). Teto: ticket máximo da sua conta. |
| external_id | texto | não | Seu identificador do pedido (até 255; letras, números, . _ : -). Único por conta. |
| description | texto | não | Até 200 caracteres. |
| payer.name | texto | sim | 3 a 120 caracteres. |
| payer.document | texto | sim | CPF (11) ou CNPJ (14), com ou sem máscara. |
| payer.email | texto | não | E-mail válido. |
| payer.phone | texto | não | DDD + número, só dígitos. |
curl -X POST "https://api.uranopag.app/v1/charges" \
-H "Authorization: Bearer up_live_SUA_CHAVE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 2f1c9a7e-5b8d-4c3a-9e21-7d6f0b4a1c55" \
-d '{
"amount_cents": 15000,
"external_id": "pedido-8842",
"description": "Pedido 8842",
"payer": { "name": "Maria da Silva", "document": "529.982.247-25", "email": "maria@email.com" }
}'Resposta 201:
{
"id": "3b0d7c1e-9f8a-4a52-8d3e-1c2b4a5f6e70",
"object": "charge",
"external_id": "pedido-8842",
"amount_cents": 15000,
"fee_cents": 449,
"net_cents": 14551,
"status": "pending",
"pix_copy_paste": "00020101021226...6304ABCD",
"end_to_end_id": null,
"expires_at": "2026-10-19T03:30:00.000Z",
"paid_at": null,
"created_at": "2026-10-18T03:30:00.000Z"
}Mostre pix_copy_paste como texto copiável (Pix Copia e Cola) e gere o QR Code a partir dele com qualquer biblioteca de QR. O pagamento é confirmado pelo webhook payment.paid.
Consultar e listar cobranças
GET /v1/charges/{id}
GET /v1/charges?page=1&limit=20&status=paid&external_id=pedido-8842limit vai de 1 a 100 (padrão 20). Filtros opcionais: status e external_id. A lista vem da mais recente para a mais antiga, com has_more indicando se há próxima página.
{ "data": [ { "id": "3b0d7c1e-...", "object": "charge", "status": "paid", "end_to_end_id": "E18236120202610180331s0123456789", "...": "..." } ], "page": 1, "limit": 20, "has_more": false }| status | Significado |
|---|---|
| pending | Aguardando pagamento. |
| paid | Pago e confirmado. |
| expired | Não pago dentro do prazo. |
| failed | Falha na emissão ou recusa do banco. |
| in_review | Pagamento em análise por contestação (MED). |
| defended | Contestação defendida: a venda segue paga. |
| refunded | Valor devolvido ao pagador. |
end_to_end_id é o código E2E do PIX (o mesmo do comprovante bancário). Ele é preenchido quando o pagamento é confirmado e a instituição o informa.
Consultar saldo
GET /v1/balance{ "object": "balance", "available_cents": 125000, "blocked_cents": 0, "updated_at": "2026-10-18T03:31:00.000Z" }available_cents é o que pode ser sacado. blocked_cents está retido (análise ou contestação) e não pode ser sacado.
Solicitar e consultar saques PIX
POST /v1/withdrawals| Campo | Tipo | Regra |
|---|---|---|
| amount_cents | inteiro | Valor debitado do saldo. A taxa de saque da conta é descontada desse valor. |
| pix_key_type | texto | cpf, cnpj, email, phone ou random |
| pix_key | texto | Chave PIX de destino. |
curl -X POST "https://api.uranopag.app/v1/withdrawals" \
-H "Authorization: Bearer up_live_SUA_CHAVE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7d6f0b4a-1c55-4c3a-9e21-2f1c9a7e5b8d" \
-d '{ "amount_cents": 50000, "pix_key_type": "email", "pix_key": "financeiro@empresa.com" }'{
"id": "9c1e2a4b-...",
"object": "withdrawal",
"amount_cents": 50000,
"fee_cents": 200,
"net_cents": 49800,
"status": "pending",
"pix_key": "financeiro@empresa.com",
"pix_key_type": "email",
"end_to_end_id": null,
"failure_reason": null,
"created_at": "2026-10-18T03:40:00.000Z",
"processed_at": null
}GET /v1/withdrawals/{id}
GET /v1/withdrawals?page=1&limit=20| status | Significado |
|---|---|
| pending | Solicitado, aguardando processamento. |
| processing | Enviado à instituição, aguardando liquidação. |
| completed | PIX liquidado no destino. |
| rejected | Recusado. O valor volta ao saldo (uma única vez). |
| failed | Não liquidado (ex.: chave inválida). O valor volta ao saldo (uma única vez). |
Regras: o saldo é debitado no pedido; existe um intervalo mínimo entre pedidos e um limite diário por conta. Pedidos repetidos com a mesma Idempotency-Key nunca geram saque duplicado.
Webhooks
Cadastre a URL (HTTPS pública) em Desenvolvedores → API e Webhooks → Webhooks e escolha os eventos. Ao criar, o painel mostra o segredo de assinatura (whsec_...) uma única vez.
| Evento | Quando |
|---|---|
| payment.paid | Cobrança paga e confirmada. |
| payment.hold | Valor retido para análise. |
| payment.released | Retenção encerrada; valor liberado. |
| payment.med | Contestação (MED) aberta: status in_review. |
| payment.med_defended | Contestação defendida: venda segue paga. |
| payment.refunded | Valor devolvido ao pagador. |
| withdrawal.completed | Saque liquidado. |
| withdrawal.rejected | Saque recusado ou falho; valor devolvido ao saldo. |
Cada entrega é um POST JSON com estes cabeçalhos:
| Cabeçalho | Conteúdo |
|---|---|
| X-UranoPag-Event-Id | Identificador estável do evento (igual em reenvios). Use para não processar duas vezes. |
| X-UranoPag-Timestamp | Unix timestamp (segundos) da assinatura. |
| X-UranoPag-Signature | sha256= + HMAC-SHA256 hex de timestamp + "." + corpo bruto, com o seu segredo. |
| X-UranoPag-Resend | true quando é um reenvio. |
{
"id": "payment.paid:3b0d7c1e-9f8a-4a52-8d3e-1c2b4a5f6e70",
"event": "payment.paid",
"created_at": "2026-10-18T03:31:02.000Z",
"data": {
"object": "charge",
"id": "3b0d7c1e-9f8a-4a52-8d3e-1c2b4a5f6e70",
"external_id": "pedido-8842",
"amount_cents": 15000,
"fee_cents": 449,
"net_cents": 14551,
"status": "paid",
"end_to_end_id": "E18236120202610180331s0123456789",
"paid_at": "2026-10-18T03:31:00.000Z",
"created_at": "2026-10-18T03:30:00.000Z"
}
}Validação da assinatura (Node.js):
import crypto from "node:crypto";
app.post("/webhooks/uranopag", express.raw({ type: "application/json" }), (req, res) => {
const ts = req.get("X-UranoPag-Timestamp") ?? "";
const got = req.get("X-UranoPag-Signature") ?? "";
const raw = req.body.toString("utf8");
const expected = "sha256=" + crypto.createHmac("sha256", process.env.URANOPAG_WEBHOOK_SECRET).update(`${ts}.${raw}`).digest("hex");
const ok = got.length === expected.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
if (!ok || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(401);
const event = JSON.parse(raw);
// processe de forma idempotente usando event.id
res.sendStatus(200);
});PHP:
<?php
$raw = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_URANOPAG_TIMESTAMP'] ?? '';
$got = $_SERVER['HTTP_X_URANOPAG_SIGNATURE'] ?? '';
$exp = 'sha256=' . hash_hmac('sha256', $ts . '.' . $raw, getenv('URANOPAG_WEBHOOK_SECRET'));
if (!hash_equals($exp, $got) || abs(time() - (int)$ts) > 300) { http_response_code(401); exit; }
$event = json_decode($raw, true);
http_response_code(200);Python:
import hmac, hashlib, os, time
raw = request.get_data()
ts = request.headers.get("X-UranoPag-Timestamp", "")
exp = "sha256=" + hmac.new(os.environ["URANOPAG_WEBHOOK_SECRET"].encode(), f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(exp, request.headers.get("X-UranoPag-Signature", "")) or abs(time.time() - int(ts or 0)) > 300:
abort(401)- Responda 2xx em até 10 segundos. Outros códigos, timeout ou erro de rede contam como falha.
- Entregas com falha podem ser reenviadas pelo painel (uma a uma ou "Reenviar falhados"). Um reenvio só repete o estado atual: nunca altera saldo, venda ou saque.
- O mesmo evento pode chegar mais de uma vez. Guarde o id e ignore repetições.
- Para liberar pedidos de valor alto, confirme também com GET /v1/charges/{id}.
Erros
Erros sempre têm o formato abaixo. Mensagens são genéricas por segurança; use o code no seu tratamento.
{ "error": { "code": "validation_error", "message": "Dados inválidos.", "fields": ["payer.document"] } }| HTTP | code | Quando |
|---|---|---|
| 400 | invalid_json / idempotency_required | Corpo inválido ou Idempotency-Key ausente. |
| 401 | unauthorized | Chave ausente, inválida ou revogada. |
| 403 | ip_not_allowed / account_restricted / onboarding_pending | IP fora da lista, conta com restrição ou cadastro não aprovado. |
| 404 | not_found | Recurso inexistente ou de outra conta. |
| 409 | idempotency_mismatch / external_id_taken | Chave de idempotência ou external_id já usados com outros dados. |
| 422 | validation_error / above_max_ticket / amount_below_fee / daily_limit / insufficient_funds | Regra de valor ou de saldo. |
| 429 | rate_limited / too_frequent | Muitas requisições (aguarde o Retry-After) ou saques próximos demais. |
| 503 | provider_unavailable | Instituição indisponível no momento. Tente de novo com a mesma Idempotency-Key. |
| 500 | internal_error | Erro interno. Tente novamente. |
Limites de uso
O limite é por chave de API, por segundo, e é definido para a sua conta (padrão: 50 requisições por segundo). Ao exceder, a API responde 429 com Retry-After: 1. Pagamentos recebidos e webhooks nunca são afetados por esse limite.
Exemplos completos
Node.js (fetch):
const res = await fetch("https://api.uranopag.app/v1/charges", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.URANOPAG_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({ amount_cents: 15000, external_id: "pedido-8842", payer: { name: "Maria da Silva", document: "52998224725" } }),
});
const charge = await res.json();
if (!res.ok) throw new Error(charge.error.code);
console.log(charge.pix_copy_paste);PHP (cURL):
<?php
$ch = curl_init('https://api.uranopag.app/v1/charges');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('URANOPAG_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode(['amount_cents' => 15000, 'payer' => ['name' => 'Maria da Silva', 'document' => '52998224725']]),
]);
$charge = json_decode(curl_exec($ch), true);Python (requests):
import os, uuid, requests
r = requests.post("https://api.uranopag.app/v1/charges",
headers={"Authorization": f"Bearer {os.environ['URANOPAG_API_KEY']}", "Idempotency-Key": str(uuid.uuid4())},
json={"amount_cents": 15000, "payer": {"name": "Maria da Silva", "document": "52998224725"}}, timeout=20)
r.raise_for_status()
print(r.json()["pix_copy_paste"])
