Wallets e apps · trocar o ramp
PIX e stablecoin para wallets e apps
Seu app já está publicado e o depósito em reais é o gargalo. Aqui cada usuário tem limite próprio, cada operação tem estado, e a tela consegue mostrar em que etapa está — sem você guardar nada.
Para quem é
- Carteira ou app já publicado, trocando o on/off-ramp brasileiro atual.
- Produto que precisa de depósito e saque na mesma integração, não dois fornecedores.
- Time que quer mostrar ao usuário onde a operação está, e não só uma ampulheta.
O problema
Num app de carteira, o depósito é onde o usuário desiste. Ele digita um valor, o provedor recusa sem dizer quanto caberia, e a tela mostra "erro". Depois de pagar, ele fica olhando um spinner sem saber se são cinco segundos ou cinco minutos. Esses dois momentos custam mais usuário que qualquer taxa.
Os dois têm resposta de API. O limite de cada pagador é consultável antes do teclado aparecer, e o status traz a linha do tempo carimbada pelo servidor, com a mediana medida de cada etapa — dá para escrever "etapa 3 de 5, costuma levar 39 s" sem inventar número.
O fluxo, ponta a ponta
- O usuário informa o CPF uma vez. Você consulta
GET /cashin/limits?payer_tax=e já mostra quanto ele pode depositar na hora e quanto cabe com retenção. - Ele escolhe o valor.
POST /cashin/chargecomcustomer_ref(o id dele no seu sistema) devolve o copia e cola. - Ele paga. O webhook
cashin.paidchega; a tela troca de etapa sem polling. - A entrega acontece e
cashin.settledtraz o hash. Se o destino forsaldo, o crédito aparece comliberar_em. - Para sacar:
POST /payouts(PIX) ouPOST /saldo/sacar-cripto(qualquer moeda do catálogo), sempre com o mesmocustomer_ref.
Endpoints usados
| Endpoint | Para quê |
|---|---|
GET /cashin/limits?payer_tax= | Quanto este usuário pode pagar agora — antes de a tela pedir o valor. |
POST /cashin/charge | Depósito, com customer_ref ligando a operação ao usuário. |
GET /cashin/{cashin_id}/status | Linha do tempo e tempos típicos por etapa: é o que alimenta o rastreador. |
GET /saldo?customer_ref= | Saldo, carência e próximas liberações daquele usuário. |
POST /saldo/sacar-cripto | Saque em qualquer moeda do catálogo, debitando do saldo do usuário. |
POST /payouts | Saque por PIX para a chave do próprio usuário ou de um terceiro. |
Exemplo que roda
# depósito do usuário: PIX identificado pelo CPF dele, cripto na carteira dele
curl -X POST https://api.luniumpay.com/cashin/charge -H "X-API-Key: $LUNIUM_KEY" \
-H "Content-Type: application/json" \
-d '{"amount_cents":15000,"asset":"usdt","chain":"polygon",
"payout_address":"0xCarteiraDoUsuario",
"payer_tax_number":"12345678909",
"customer_ref":"user_8842","external_id":"dep-8842-17"}'
# limite daquele usuário ANTES de mostrar o teclado de valor
curl "https://api.luniumpay.com/cashin/limits?payer_tax=12345678909" -H "X-API-Key: $LUNIUM_KEY"async function limiteDoUsuario(cpf) {
const r = await fetch(`${API}/cashin/limits?payer_tax=${cpf}`, {
headers: { "X-API-Key": process.env.LUNIUM_KEY },
});
const l = await r.json();
return { naHora: l.instant_available_cents, comRetencao: l.held_qr?.available_cents ?? 0 };
}
async function depositar({ cents, cpf, carteira, userId, idempotencia }) {
const r = await fetch(`${API}/cashin/charge`, {
method: "POST",
headers: { "X-API-Key": process.env.LUNIUM_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
amount_cents: cents, asset: "usdt", chain: "polygon",
payout_address: carteira, payer_tax_number: cpf,
customer_ref: userId, external_id: idempotencia,
}),
});
if (r.status === 403) throw new Error("acima do limite deste pagador");
return r.json();
}import os, requests
API, H = "https://api.luniumpay.com", {"X-API-Key": os.environ["LUNIUM_KEY"]}
def limite_do_usuario(cpf):
l = requests.get(f"{API}/cashin/limits", headers=H, params={"payer_tax": cpf}, timeout=20).json()
return l["instant_available_cents"], (l.get("held_qr") or {}).get("available_cents", 0)
def depositar(cents, cpf, carteira, user_id, idempotencia):
r = requests.post(f"{API}/cashin/charge", headers=H, timeout=30, json={
"amount_cents": cents, "asset": "usdt", "chain": "polygon",
"payout_address": carteira, "payer_tax_number": cpf,
"customer_ref": user_id, "external_id": idempotencia})
if r.status_code == 403:
raise RuntimeError("acima do limite deste pagador")
return r.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
Acima do limite do pagador: 403 limite_do_pagador com acao: "corrigir" e
quanto cabe. Mostre o número, não a palavra "erro" — foi o que mais converteu nos apps que já usam.
Abaixo do mínimo da rota: a resposta traz o mínimo estimado em reais e nada é debitado. Ofereça o valor certo em um toque.
Primeiro depósito de uma subconta: saques ficam travados por 24 h (423
carencia_primeiro_deposito com libera_em). Diga a hora, não "tente mais tarde".
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
O caminho crítico do app são duas chamadas: limite e cobrança. As duas existem no sandbox, com o mesmo contrato — dá para montar a tela inteira antes de existir contrato comercial.
Perguntas que os integradores fazem
Preciso guardar o estado das operações?
Não. GET /cashin/{id}/status traz a linha do tempo carimbada pelo servidor e os tempos típicos por etapa; o webhook avisa cada transição. O seu banco guarda o external_id e o resto vem daqui.
Como separo os usuários?
Pelo customer_ref. Ele viaja na cobrança, no saldo, no saque e no extrato — o livro-razão mantém cada cliente separado sem você criar chave por usuário.
Consigo saque em outras moedas além de USDT?
Sim. POST /saldo/sacar-cripto entrega qualquer moeda do catálogo pelo mesmo trilho da compra, com o mínimo da rota checado antes do débito.
O usuário pode depositar mais que o limite inicial?
Pode: acima da escada instantânea o QR ainda é aceito e o provedor retém 24 h. Se o seu produto não pode esperar, mande allow_hold: false e receba um 403 dizendo quanto sai na hora.
Contrato OpenAPI 1.25.0 · fatos desta página em product-truth.json · status