Infraestrutura · subcontas e custódia
Infraestrutura PIX e stablecoin, com a sua marca
A aba "Depositar e Sacar" do seu produto, sem você virar instituição de pagamento. Cada cliente final é uma subconta no livro-razão, identificada pelo id que já existe no seu sistema.
Para quem é
- Produto que quer guardar reais dos clientes finais sem construir custódia do zero.
- Plataforma que precisa de depósito, saldo, saque PIX e saque em cripto no mesmo lugar.
- Quem já tem usuários e precisa separar o dinheiro de cada um com rastro auditável.
O problema
Guardar dinheiro de terceiros é onde a maioria dos produtos trava. Não é só receber: é separar por cliente, segurar o que ainda não pode sair, provar cada movimento e conseguir explicar um saldo meses depois. Construir isso do zero significa livro-razão, conciliação e uma auditoria que ninguém queria fazer agora.
Aqui a separação é um campo. customer_ref viaja na cobrança, no saldo, no saque e no extrato; o livro-razão é
append-only e cada linha tem origem. Você entrega a experiência com a sua marca e não escreve conciliação nenhuma.
O fluxo, ponta a ponta
- Depositar:
POST /cashin/chargecomdestino: "saldo"e ocustomer_refdo cliente. O PIX vira saldo em reais na subconta dele. - Ver:
GET /saldo?customer_ref=traz disponível, bloqueado, carência e próximas liberações.GET /saldo/extratotraz movimento a movimento. - Sacar em reais:
POST /payoutscom o mesmocustomer_ref— debita da subconta certa. - Sacar em cripto:
POST /saldo/sacar-criptoentrega qualquer moeda do catálogo, com o mínimo da rota checado antes do débito. - Mover entre clientes:
POST /saldo/transferirresolve no livro-razão, sem tocar em trilho externo.
Endpoints usados
| Endpoint | Para quê |
|---|---|
POST /cashin/charge (destino: saldo) | Depósito em reais na subconta do cliente final. |
GET /saldo?customer_ref= | Saldo, carência, taxas vigentes e próximas liberações. |
GET /saldo/extrato | Movimento a movimento, com origem de cada linha. |
GET /saldo/clientes · /saldo/consolidado | Todas as subcontas e o total sob custódia. |
POST /saldo/transferir | Transferência entre subcontas, dentro do livro-razão. |
POST /saldo/sacar-cripto · POST /payouts | As duas saídas: cripto na carteira do cliente ou PIX na chave dele. |
Exemplo que roda
# depósito na subconta de um cliente final
curl -X POST https://api.luniumpay.com/cashin/charge -H "X-API-Key: $LUNIUM_KEY" \
-H "Content-Type: application/json" \
-d '{"amount_cents":50000,"destino":"saldo","customer_ref":"cliente_412",
"payer_tax_number":"12345678909","external_id":"dep-412-88"}'
# saldo daquele cliente
curl "https://api.luniumpay.com/saldo?customer_ref=cliente_412" -H "X-API-Key: $LUNIUM_KEY"
# transferência entre subcontas, sem sair do livro-razão
curl -X POST https://api.luniumpay.com/saldo/transferir -H "X-API-Key: $LUNIUM_KEY" \
-H "Content-Type: application/json" \
-d '{"from_customer_ref":"cliente_412","to_customer_ref":"cliente_907",
"amount_cents":15000,"external_id":"tr-9912"}'const h = { "X-API-Key": process.env.LUNIUM_KEY, "Content-Type": "application/json" };
const api = (rota, corpo) => fetch(`${API}${rota}`, corpo
? { method: "POST", headers: h, body: JSON.stringify(corpo) }
: { headers: h }).then((r) => r.json());
export const depositar = (cliente, cents, cpf, id) => api("/cashin/charge", {
amount_cents: cents, destino: "saldo", customer_ref: cliente,
payer_tax_number: cpf, external_id: id,
});
export const saldoDe = (cliente) => api(`/saldo?customer_ref=${encodeURIComponent(cliente)}`);
export const sacarPix = (cliente, cents, chave, tipo, doc, id) => api("/payouts", {
amount_cents: cents, pix_key: chave, pix_key_type: tipo,
tax_number: doc, customer_ref: cliente, external_id: id,
});
export const sacarCripto = (cliente, cents, ativo, rede, carteira, doc, id) => api("/saldo/sacar-cripto", {
amount_cents: cents, asset: ativo, chain: rede, payout_address: carteira,
tax_number: doc, customer_ref: cliente, external_id: id,
});import os, requests
API, H = "https://api.luniumpay.com", {"X-API-Key": os.environ["LUNIUM_KEY"]}
def depositar(cliente, cents, cpf, ident):
return requests.post(f"{API}/cashin/charge", headers=H, timeout=30, json={
"amount_cents": cents, "destino": "saldo", "customer_ref": cliente,
"payer_tax_number": cpf, "external_id": ident}).json()
def saldo_de(cliente):
return requests.get(f"{API}/saldo", headers=H, params={"customer_ref": cliente}, timeout=20).json()
def transferir(de, para, cents, ident):
return requests.post(f"{API}/saldo/transferir", headers=H, timeout=30, json={
"from_customer_ref": de, "to_customer_ref": para,
"amount_cents": cents, "external_id": ident}).json()Sandbox: teste antes de gastar
Uma chamada, sem cadastro, sem cartão. A chave lun_test_ exercita a superfície inteira
sem mover dinheiro nenhum.
curl -X POST https://api.luniumpay.com/keys/sandbox \
-H "Content-Type: application/json" \
-d '{"name":"meu-teste"}'
O que o sandbox não prova: liquidação real. Nenhuma cripto se move e nenhum PIX é pago — ele serve para acertar o contrato, não para medir o trilho.
Ativos e redes
Instantâneo (trilho próprio): USDT na Polygon e USDC na Polygon — liquidação em segundos, sem corretora no caminho.
Via corretora: 1.478 ativos em 241 redes na venda (1.668 pares ativo×rede) e 1.795 rotas na compra. O prazo é o número de confirmações que a rede exige, não uma escolha nossa.
Redes de endereço compartilhado (ALGO, ATOM, EOS, HBAR, KAVA, LUNA, TON, XLM, XRP): o depósito precisa levar o memo. Na venda ele vem
em deposit_tag no aceite; na compra você manda payout_tag junto do endereço.
O catálogo muda sozinho conforme redes entram e saem. Leia de GET /catalog e GET /cashin/catalog em vez de manter lista no código.
Limites e retenções
| O quê | Valor |
|---|---|
| Compra (cash-in) e saque PIX, por operação | R$ 1,00 a R$ 6.000,00 |
| Venda (cash-out), por operação | a partir de R$ 6,00 |
| Validade do QR | 15 minutos |
| Escada por pagador (CPF/CNPJ de quem paga) | R$ 60,00 na estreia → R$ 200,00 nas primeiras 24 h → R$ 6.000,00/dia |
| Acima da escada | o QR ainda é aceito e o provedor retém 24 h (D+1), até R$ 6.000,00/dia por pagador |
A escada é por documento de quem paga, não por conta que recebe: dez pagadores são dez limites
independentes. É isso que faz o produto servir a e-commerce e marketplace. Consulte o disponível de um
pagador antes de cobrar em GET /cashin/limits?payer_tax=, e leia os limites da sua chave em
GET /keys/me — tetos acima do tier são contratados.
Medições, não promessas
Medido em produção, janela de 90 dias, em 07/09/2026:
| Trecho | Mediana | p90 | Amostra |
|---|---|---|---|
| PIX recebido → stablecoin entregue (Polygon) | 6 s | 70 s | 112 operações |
| Aceite da cotação → PIX pago (Polygon) | 81 s | 580 s | 27 operações |
Fora da Polygon o relógio é o da rede: o prazo é o número de confirmações que a corretora
exige, e está em eta no catálogo. Não prometemos “instantâneo” fora do trilho próprio.
Idempotência
Mande external_id em toda operação que move dinheiro. Repetir a mesma chamada devolve a
mesma operação, nunca uma segunda; os mesmos parâmetros com id diferente criam duas, e o mesmo id
com parâmetros diferentes responde 409 external_id_divergente em vez de adivinhar.
Timeout não é recusa. Se a chamada não respondeu, consulte pelo external_id
antes de repetir — a operação pode existir.
Webhooks e reconciliação
Cada transição dispara um evento assinado em HMAC SHA-256 com timestamp (header
X-Lunium-Signature, formato t=…,v1=…) — o timestamp é o que impede replay de uma
entrega capturada. Confira a assinatura antes de confiar no corpo.
Webhook é o sinal principal; polling é opcional. Quando precisar pesquisar, o status traz a
timeline carimbada pelo servidor — desenhe a etapa a partir dela, porque o cronômetro do
seu polling mede a sua rede, não a operação.
Não respondeu 2xx? A entrega é retentada com backoff. Você reenvia à mão em
POST /webhooks/deliveries/{event_id}/retry e testa a sua URL em POST /webhooks/test.
Quando dá errado
Carência: cada depósito só pode sair depois de carencia_horas, e o primeiro depósito de uma
subconta trava saques por 24 h. As duas aparecem em GET /saldo e no 423 com libera_em —
mostre a hora ao cliente, não um erro genérico.
Saldo insuficiente: 402 com disponível e bloqueado separados. Nada é debitado.
Saque abaixo do mínimo da moeda: recusado antes do débito, com o mínimo estimado em reais — o saldo do cliente nunca sai para uma entrega que morreria na corretora.
Segurança
- webhook assinado — Todo webhook vai assinado em HMAC SHA-256 com timestamp (header X-Lunium-Signature, formato t=…,v1=…), o que impede replay de uma entrega capturada.
- idempotencia — external_id em cash-in, cash-out e payout: repetir a mesma chamada devolve a MESMA operação, nunca uma segunda. Parâmetros diferentes no mesmo external_id respondem 409.
- egress validado — A URL de webhook é resolvida e validada antes de cada entrega: endereço interno, metadata de nuvem e redirect para rede privada são recusados, e a conexão é fixada no IP validado.
- retencao fail closed — A retenção é verificada na liquidação e repetida na reivindicação SQL: nenhum caminho (worker, consulta de status, fila) consegue liquidar antes da hora.
- payout duravel — O saque grava a operação e debita o saldo na MESMA transação, antes de qualquer chamada ao provedor: não existe débito sem ordem para reconciliar.
- verificacao aberta — GET /v1/verificar/{e2e} confere um PIX liquidado sem chave nenhuma — a contraparte não precisa confiar na nossa palavra.
A verificação aberta é a que mais importa numa negociação: a sua contraparte confere o PIX sozinha, sem chave e sem depender da nossa palavra.
Tempo até a primeira integração
A tela de depósito é uma chamada; a de saldo, outra. O que costuma levar mais tempo é decidir a sua política de carência — a nossa é configurável por chave e visível na API, então a sua interface pode explicá-la sem você hard-codar nada.
Perguntas que os integradores fazem
Isso me torna uma instituição de pagamento?
A custódia e a liquidação são nossas; você opera a experiência e a relação com o cliente final. O enquadramento regulatório do seu produto é uma decisão sua e do seu jurídico — não vendemos licença nem opinião jurídica.
Como separo o dinheiro de cada usuário?
Pelo customer_ref, o mesmo id que você já usa. O livro-razão é append-only e cada movimento tem origem rastreável; GET /saldo/consolidado fecha o total sob custódia.
Posso definir a carência?
Sim, por chave. O valor vigente aparece em GET /saldo como carencia_horas, e cada crédito traz liberar_em — a sua interface lê de lá.
O cliente final pode sacar em cripto?
Pode: POST /saldo/sacar-cripto entrega qualquer moeda do catálogo pelo mesmo trilho da compra, com o mínimo da rota conferido antes de debitar.
Contrato OpenAPI 1.25.0 · fatos desta página em product-truth.json · status