Infraestructura · subcuentas y custodia
Infraestructura de PIX y stablecoins con tu marca
La pestaña «Depositar y retirar» de tu producto, sin convertirte en una entidad de pagos. Cada cliente final es una subcuenta en el libro mayor, identificada con el id que ya existe en tu sistema.
A quién va dirigido
- Productos que quieren guardar los reales brasileños de los clientes finales sin construir la custodia desde cero.
- Plataforma que necesita depósito, saldo, retiro PIX y retiro en cripto en el mismo lugar.
- Quienes ya tienen usuarios y necesitan separar el dinero de cada uno con un registro auditable.
El problema
Guardar dinero de terceros es donde la mayoría de los productos se atascan. No es solo recibir: es separar por cliente, mantener lo que aún no puede retirarse, probar cada movimiento y poder explicar un saldo meses después. Construir esto desde cero implica libro mayor, conciliación y una auditoría que nadie quería hacer ahora.
Aquí la separación es un campo. customer_ref se incluye en la solicitud de cobro, el saldo, el retiro y los movimientos; el libro mayor solo permite añadir registros y cada asiento tiene un origen. Ofreces la experiencia con tu marca sin tener que implementar la conciliación.
El flujo de principio a fin
- Depositar:
POST /cashin/chargecondestino: "saldo"y elcustomer_refdel cliente. El PIX se convierte en saldo en reales brasileños en su subcuenta. - Ver:
GET /saldo?customer_ref=incluye el saldo disponible, el bloqueado y las próximas liberaciones.GET /saldo/extratoincluye cada movimiento. - Retirar en reales brasileños:
POST /payoutscon el mismocustomer_ref— debita la subcuenta correcta. - Retirar en cripto:
POST /saldo/sacar-criptoentrega cualquier moneda del catálogo, con el mínimo de la ruta verificado antes del débito. - Mover fondos entre clientes:
POST /saldo/transferirlo resuelve en el libro mayor sin usar una vía de procesamiento externa.
Endpoints utilizados
| Endpoint | Para qué |
|---|---|
POST /cashin/charge (destino: saldo) | Depósito en reales brasileños en la subcuenta del cliente final. |
GET /saldo?customer_ref= | Saldo, tarifas vigentes y próximas liberaciones. |
GET /saldo/extrato | Movimiento a movimiento, con origen de cada línea. |
GET /saldo/clientes · /saldo/consolidado | Todas las subcuentas y el total bajo custodia. |
POST /saldo/transferir | Transferencia entre subcuentas, dentro del libro mayor. |
POST /saldo/sacar-cripto · POST /payouts | Las dos salidas: criptomonedas en la billetera del cliente o PIX en su clave. |
Ejemplo ejecutable
# depósito en la subcuenta de un 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 de ese cliente
curl "https://api.luniumpay.com/saldo?customer_ref=cliente_412" -H "X-API-Key: $LUNIUM_KEY"
# transferencia entre subcuentas, sin salir del libro mayor
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: prueba antes de gastar
Una llamada, sin aprobación previa ni tarjeta; solo es obligatorio tu correo (al que enviamos el manual, el diagrama de flujo y la guía de integración paso a paso). La clave lun_test_ permite probar toda la interfaz sin mover dinero.
curl -X POST https://api.luniumpay.com/keys/sandbox \
-H "Content-Type: application/json" \
-d '{"email":"voce@empresa.com","name":"meu-teste"}'
Lo que el sandbox no demuestra: liquidación real. No se mueven criptomonedas ni se paga ningún PIX; sirve para ajustar el contrato, no para medir la vía de procesamiento.
Activos y redes
El catálogo de entrada de criptomonedas es GET /catalog; el de entrega tras Pix es GET /cashin/catalog. Consulta el activo y la red del flujo elegido antes de cotizar. La disponibilidad y los mínimos pueden cambiar.
Usa deposit_tag en el depósito cuando la aceptación indique un memo. En la entrega, envía payout_tag cuando lo requiera la ruta. El manual de la API describe cada campo.
Límites y retenciones
Consulta GET /keys/me para conocer los límites y las tarifas de la cuenta, GET /cashin/limits?payer_tax=... para el pagador y las vistas previas para el mínimo de la ruta. No uses un límite máximo fijo compartido entre cash-in, cash-out y payout.
Una solicitud de cobro pagada puede quedar retenida antes de la liquidación. Usa los estados, la cronología y los eventos firmados para distinguir entre cobro, saldo disponible y entrega final. La validez de la operación figura en expires_at.
Mediciones, no promesas
El plazo depende del flujo, la ruta disponible, las confirmaciones de la red y las posibles retenciones. Sigue la operación hasta su estado final y consulta el estado del servicio. El tiempo de una simulación no mide una liquidación real.
Idempotencia
Envía external_id en cada operación que mueve dinero. Repetir la misma llamada devuelve la misma operación, nunca una segunda; los mismos parámetros con un id distinto crean dos, y el mismo id con parámetros distintos devuelve 409 external_id_divergente en lugar de intentar adivinar.
Un timeout no es un rechazo. Si la llamada no respondió, consulta por el external_id antes de repetirla: la operación puede existir.
Webhooks y conciliación
Cada transición genera un evento firmado con HMAC SHA-256 y una marca de tiempo (cabecera X-Lunium-Signature, formato t=…,v1=…). La marca de tiempo impide reutilizar una entrega capturada. Verifica la firma antes de confiar en el cuerpo.
El webhook es la señal principal; el polling es opcional. Cuando necesites consultar, el estado incluye la timeline marcada por el servidor — representa la etapa a partir de ella, porque el cronómetro de tu polling mide tu red, no la operación.
¿No respondió con 2xx? Se reintenta la entrega con esperas progresivas. Puedes reenviarla manualmente en POST /webhooks/deliveries/{event_id}/retry y probar tu URL en POST /webhooks/test.
Cuando algo falla
Retención del primer PIX: el abono está disponible en cuanto se confirma el Pix; solo el primer PIX de un documento por encima de R$ 100,00 se retiene durante 24 h en el proveedor y aparece como delayed en GET /saldo, liberándose automáticamente con saldo.liberado — muestra la hora al cliente, no un error genérico.
Saldo insuficiente: 402 con el saldo disponible y el bloqueado separados. No se debita nada.
Retiro por debajo del mínimo de la moneda: rechazado antes del débito, con el mínimo estimado en reales brasileños: el saldo del cliente nunca se envía a una entrega que se rechazaría en la plataforma de intercambio.
Seguridad
- webhook firmado — Cada webhook se firma con HMAC SHA-256 y una marca de tiempo (cabecera X-Lunium-Signature, formato t=…,v1=…), lo que impide reutilizar una entrega capturada.
- idempotencia — external_id en cash-in, cash-out y payout: repetir la misma llamada devuelve la MISMA operación, nunca una segunda. Los parámetros distintos con el mismo external_id devuelven 409.
- tráfico de salida validado — La URL del webhook se resuelve y valida antes de cada entrega: se rechazan direcciones internas, metadatos de la nube y redirecciones a redes privadas, y la conexión se fija en la IP validada.
- retención con bloqueo ante fallos — La retención se verifica al liquidar y de nuevo al reclamar la operación en SQL: ninguna vía (worker, consulta de estado o cola) puede liquidar antes de tiempo.
- payout duradero — El retiro registra la operación y debita el saldo en la MISMA transacción, antes de cualquier llamada al proveedor: no existe un débito sin una orden que conciliar.
- verificación abierta — GET /v1/verificar/{e2e} comprueba un PIX liquidado sin ninguna clave; la contraparte no tiene que confiar en nuestra palabra.
La verificación abierta es especialmente importante en una negociación: tu contraparte comprueba el PIX por su cuenta, sin clave y sin depender de nuestra palabra.
Tiempo hasta la primera integración
La pantalla de depósito requiere una llamada; la de saldo, otra. Lo que suele llevar más tiempo es diseñar tu pantalla de saldo. Las tarifas y retenciones vigentes están visibles en la API, por lo que tu interfaz puede explicarlas sin fijar valores en el código.
Preguntas de los integradores
¿Esto me convierte en una entidad de pagos?
La custodia y la liquidación las gestionamos nosotros; tú gestionas la experiencia y la relación con el cliente final. La clasificación regulatoria de tu producto es una decisión tuya y de tu equipo jurídico: no vendemos licencias ni asesoramiento jurídico.
¿Cómo separo el dinero de cada usuario?
Mediante customer_ref, el mismo id que ya usas. El libro mayor solo permite añadir registros y cada movimiento tiene un origen rastreable; GET /saldo/consolidado permite conciliar el total en custodia.
¿Puedo definir el período de espera?
De forma predeterminada no hay período de espera: el abono está disponible en cuanto se confirma el Pix (GET /saldo muestra carencia_horas: 0). El período de espera solo se aplica por acuerdo contractual; si existe, tu interfaz lo consulta allí.
¿Puede el cliente final retirar en cripto?
Sí: POST /saldo/sacar-cripto entrega cualquier moneda del catálogo por la misma vía de procesamiento de la compra, comprobando el mínimo de la ruta antes de debitar.
Contrato OpenAPI vigente · datos de esta página en product-truth.json · estado