Lunium.Developers
Browse and search
Partner documentation

Sandbox: PIX, custody and crypto

Create a test key, simulate complete payment flows and verify signed webhooks without moving real funds.

Revision 2026-09-21.en1 · API 1.33.1 · OpenAPI · Operational status
English integration guide. Use a sandbox key for every simulated operation.

Use https://api.luniumpay.com with a lun_test_ key. Every financial operation is simulated. An incoming QR contains SANDBOX:NAO_PAGAVEL: and cannot be paid through a bank. Confirm payment through the simulation API. No withdrawal is sent to an exchange or blockchain.

Start with a test key

curl --fail-with-body https://api.luniumpay.com/keys/sandbox \
  -H 'Content-Type: application/json' \
  -d '{"name":"My sandbox integration"}'

Save the returned api_key in your backend environment as LUNIUM_API_KEY. Confirm key.sandbox=true using GET /keys/me before creating test operations. Do not put the key in browser code or paste it into an AI conversation.

To run the examples in your own environment, download the English starter kit:

node sandbox.mjs all
# Or Python 3.9+, with no third-party dependencies:
python3 sandbox.py all

The kit creates a key in memory, checks GET /keys/me and rejects production credentials. It also accepts an existing sandbox key through LUNIUM_API_KEY and never prints credentials. Replace all with custody, cashin, payout or cashout to run one journey.

For the public AI connection, call lunium_start_sandbox_demo with a random, stable request_id and flow set to custody, cashin, payout or cashout. Continue with lunium_get_sandbox_demo; it may create the simulated withdrawal already included in the selected journey after confirming the credit. Read the AI integration guide.

Journeys and completion

Journey Create Confirm and track Success
PIX → BRL custody POST /cashin/charge, destino: "saldo" POST /sandbox/cashin/{cashin_id}/pay, then /cashin/{cashin_id}/status and /saldo status=paid, settlement_status=sent; available funds in the correct subaccount
PIX → BTC, USDC or another test-catalog route POST /cashin/charge, destino: "cripto", asset, chain, payout_address Same payment simulation and cash-in status lookup paid and sent; hash begins with sandbox:, with no real transaction
Custody → crypto POST /saldo/sacar-cripto, amount_cents, tax_number, asset, chain, payout_address /cashin/{cashin_id}/status paid and sent; debit includes fee_cents
Custody → PIX POST /payouts, amount_cents, pix_key GET /payouts/{payout_id} status=sent; fictitious E2E with ISPB 00000000
Internal transfer POST /saldo/transferir, from_customer_ref, to_customer_ref, amount_cents /saldo/extrato, /saldo/clientes, /saldo/consolidado Atomic debit and credit; total custody balance conserved
External crypto → PIX POST /cash-outs, then POST /cash-outs/{cashout_id}/accept GET /cash-outs/{cashout_id} state=COMPLETED; deposit is also simulated

Custody does not require a fixed wallet. The balance is BRL: select asset, network, wallet and memo at withdrawal. The kit exercises native BTC, USDC on Polygon and USDC withdrawal on Base. Read the test catalog for other supported routes. Use chain for cash-in and crypto withdrawals; use network for cash-out.

Receive into custody without a wallet

After verifying your sandbox key, create the charge below. The tax ID is synthetic and is only for sandbox use. 100000 BRL cents means R$1,000.00.

curl --fail-with-body https://api.luniumpay.com/cashin/charge \
  -H "X-API-Key: $LUNIUM_API_KEY" -H 'Content-Type: application/json' \
  -d '{"amount_cents":100000,"payer_tax_number":"12345678901","destino":"saldo","customer_ref":"test-customer","external_id":"sandbox-deposit-001"}'

Save the returned cashin_id as CASHIN_ID, then simulate payment:

curl --fail-with-body -X POST "https://api.luniumpay.com/sandbox/cashin/$CASHIN_ID/pay" \
  -H "X-API-Key: $LUNIUM_API_KEY" -H 'Content-Type: application/json' -d '{}'

Query /cashin/{cashin_id}/status until paid and sent, typically after about six seconds. Then read /saldo?customer_ref=test-customer. The credit is net of the key's fees; do not compare it with the gross charge amount. Repeating the payment confirmation does not duplicate credit.

Deterministic cash-in, custody and payout scenarios

Supply sandbox_scenario when creating an operation. Read GET /sandbox for the scenario matrix supported by each flow.

Scenario Result
success Success; default when omitted
delayed Approximately 30-second delay before processing continues
held Custody deposit only: credit blocked for 30 seconds, then available
expired Cash-in expires when payment is simulated
payer_mismatch Cash-in refunded for a payer mismatch, without credit or delivery
refunded Cash-in refunded without credit; payout refunded with a single reversal
failed Payment fails; payout debit reversed once
settlement_failed Payment may be confirmed but delivery fails; withdrawal debit and fees reversed
settlement_uncertain Delivery remains incerto; payout remains processing. No reversal before reconciliation
route_unavailable Creation refused without a debit
provider_unavailable Simulated HTTP 503 with no operation created; remains unsuccessful while this scenario is selected

To resolve uncertainty in sandbox only, call POST /sandbox/operations/{operation_id}/resolve with {"outcome":"sent"} or {"outcome":"failed"}. Repeating the same resolution is idempotent. The operation must belong to this key and already have an uncertain result. There is no simulated confirmation route for production payments.

Also test invalid bodies, asset/network minimums, insufficient funds, concurrent spending, a blocked account, another key's operation ID and an external_id reused with different parameters. Keep one business intent, a stable external_id and the same request body for retries. Charges and crypto withdrawals share an idempotency namespace. After a timeout, recover by operation ID or a list filtered by external_id.

Crypto-to-PIX scenarios

Cash-out uses the last two decimal places of amount, or brl_amount for reverse quotes: .00 success, .01 delay, .02 failure, .03 expiration, .04 limit, .05 slow processing, .06 provider refusal, .07 first acceptance returns 503 and the second succeeds, .08 payer limit. Preview creates no operation. Never send crypto to an address returned by the simulator.

Real webhook delivery for simulated events

Configure a public HTTPS URL you control using PATCH /keys/me and webhook_url. Store webhook_secret securely. Sandbox events use the normal delivery queue and signature and contain sandbox:true. Verify HMAC over the raw request bytes, the timestamp and event_id, and handle duplicate events as described in the webhook manual. Real HTTP delivery does not mean real financial settlement.

GET /sandbox/events shows generated events even without a configured callback. GET /webhooks/deliveries shows delivery attempts; POST /webhooks/deliveries/{event_id}/retry retries a delivery. Events generated before callback configuration are not sent retroactively: create a new operation or use /webhooks/test. Cash-in emits cashin.paid, cashin.settled or the relevant failure. Holds, releases and transfers have their own events. Payout reports its final result; cash-out reports progress and completion.

Configure the receiver before simulating payment. Confirm separately that an event was generated, delivered over HTTP and processed by your application. The starter scripts alone do not prove that your backend handled a webhook.

Persistence, prices and test boundaries

Cash-in, custody, payouts and transfers persist separately from the real financial ledger, including across service restarts. Cash-out is persisted for two hours. There is a limit of 1,000 operations per key for the new journeys and 1,000 cash-outs. POST /sandbox/reset with {"confirm":true} deletes only this key's simulated state; previously queued webhook deliveries retain their history.

Quotes and catalogs are synthetic snapshots identified in responses. Production prices, minimums, availability and risk checks can differ. Sandbox calls no bank, exchange or wallet. Its payout provider fee is 100 BRL cents, reported by /saldo; this is not a live provider price. The payer ladder does not query real personal history; /cashin/limits describes the difference. Hold durations are compressed for testing.

The simulator supports named catalog assets. It does not simulate arbitrary DEX token contracts, bank decoding of dynamic PIX QRs, blockchain execution or private provider policies. Before production, reread /keys/me, /saldo, limits and catalogs with the live key. A pilot using a real PIX payment and a real crypto withdrawal moves actual funds and requires an authorized amount and recipient. A completed demo does not certify provider liquidity or your full production integration.

Integration manual · API reference · English OpenAPI · Human support