Visão geral
Webhooks assinados, idempotência nativa, um bot no Telegram que avisa cada centavo em tempo real e tira dúvidas de integração 24/7 — e uma documentação que cabe numa página.
Cash-in: PIX entra, USDT sai na Polygon. Cash-out: USDT e USDC nas principais redes viram PIX — Polygon em segundos, e Ethereum, BNB Chain, Arbitrum, Optimism, Base, Avalanche, TON, Aptos, NEAR e outras no prazo de confirmação de cada rede (tabela completa).
Porque aqui o PIX já vira stablecoin na sua carteira (ou o contrário) numa chamada só: cotação, QR, confirmação, conversão e envio on-chain são um pipeline nosso, com garantias de dinheiro (nunca pagamos duas vezes, nunca enviamos fração que não recebemos) e reconciliação automática 24/7 — mesmo que o seu servidor caia no meio.
Integre em 60 segundos
- Abra o seu grupo de operação — Sandbox MCP (agentes) fortemente recomendado, leva 1 minuto e é o que garante que você seja avisado quando algo acontecer com o seu dinheiro.
- Crie sua chave — fale com o bot no Telegram (diga "meu nome é SuaEmpresa" e a
credencial nasce no chat) ou um
POST /keys(abaixo tem um botão que faz isso agora). Sem espera, sem humano no caminho — ninguém gera credencial por você, você mesmo gera. - Crie uma cobrança —
POST /cashin/chargecom valor e CPF/CNPJ do pagador. Volta um PIX copia-e-cola pronto pra tela. - Receba USDT — pagou, nós liquidamos na Polygon e te avisamos por webhook e Telegram, com o hash da transação.
Sandbox — teste tudo antes de gastar qualquer coisa
Uma chamada, sem chave de API — este é o único endpoint que não pede credencial (limitado por IP). Sem nome, sem e-mail, sem carteira, sem aprovação.
curl -s -X POST https://api.luniumpay.com/keys/sandbox \
-H 'Content-Type: application/json' -d '{"name":"meu primeiro teste"}'
Você recebe uma chave lun_test_… que roda o cash-out inteiro sem um centavo se
mover, na mesma URL base. O que o sandbox cobre: POST /cash-outs, o aceite e todos os
estados até o desfecho. O que ele não cobre: cash-in (a chave de teste recusa com
sandbox_sem_cashin — criar cobrança geraria um QR pagável de verdade), payouts (dependem de
volume liquidado) e webhooks automáticos: ordens de sandbox não disparam webhook sozinhas —
mas uma chave de sandbox pode chamar POST /webhooks/test e validar assinatura e
transporte do seu handler antes de qualquer operação real. Código escrito aqui foi desenhado para ir
a produção sem mudança: mesmas formas, mesmos estados, mesmo contrato de erro, mesmas validações.
Limites e comportamento financeiro reais são os da sua chave de produção — leia-os de
GET /keys/me, não desta página.
A ordem não conclui na hora — ela caminha pelos estados reais em cerca de 15 segundos. É de propósito: você precisa desse laço de acompanhamento (ou do webhook) em produção de qualquer jeito, então escreve agora.
Gatilhos determinísticos
Testar só o caminho feliz é como a integração quebra no primeiro dia. As duas primeiras casas
decimais do amount escolhem o desfecho — sem sorte envolvida, dá para afirmar em CI:
amount terminado em | o que acontece | o que isso prova |
|---|---|---|
.01 | vai para delayed e conclui sozinha | seu código não chama de falha um pagamento retido |
.02 | falha | seu caminho de erro roda |
.03 | a cotação expira em 5s | você recota em vez de insistir |
.04 | recusa por limite, com limits preenchido | você lê limits.min_amount em vez de chutar |
.05 | conclui em ~2 minutos | seu polling tem paciência |
Vale ensaiar também: reusar um external_id com destino diferente devolve
409 — o caso que mais confunde integrador, e é melhor encontrá-lo aqui.
A ordem de sandbox gera um E2E bem formado com ISPB 00000000, que nenhuma instituição
real possui — então nunca pode ser confundido com pagamento de verdade.
GET /v1/verificar/{e2e} responde para ele, marcado com sandbox: true.
Ordens de teste somem 2 horas depois de criadas. Nunca envie cripto para um
deposit_address de sandbox: ele não tem dono e os fundos seriam perdidos.
Servidor MCP — para agentes de IA
https://api.luniumpay.com/mcp
Streamable HTTP, revisão 2025-06-18, sem estado. Adicione como conector MCP remoto em
qualquer host que fale o protocolo.
Funciona sem credencial nenhuma. Conecte sem chave e lunium_verify_pix_payment
já está disponível — confirma que um PIX foi liquidado a partir do E2E, inclusive de um pagamento que
não é seu. É a forma de conferir o que a contraparte alega sem ter conta e sem confiar nela.
As demais ferramentas usam a X-API-Key enviada pelo host como header. Sem ela,
respondem erro: "chave_ausente" com acao: "parar" — não somem da lista, para
o agente conseguir avisar o usuário em vez de concluir que a capacidade não existe.
São oito ferramentas: verificar pagamento · listar o que liquida agora · consultar limite do pagador · cotar venda · confirmar · criar cobrança · acompanhar venda · acompanhar cobrança.
As duas que movem dinheiro de verdade vêm marcadas com destructiveHint: true, para o
host poder exigir aprovação humana, e confirmar uma venda exige um token amarrado ao valor, à rede e
ao destino que foram cotados. Vender é em dois passos de propósito: cotar não compromete nada,
confirmar é irreversível.
Grupo de operação FORTEMENTE RECOMENDADO
São três passos, uma vez só:
- Crie um grupo no Telegram com o nome do seu projeto. Sugestão que ajuda a gente a te
identificar na hora:
Lunium <> SuaEmpresa. - Adicione os dois (ambos necessários pro grupo cumprir o papel):
—
@LuniumNotifyBot· o agente. Avisa tudo em tempo real, responde dúvida de integração e executa comando (/cobrar,/status,/extrato).—@luniumB2C· o time da Lunium. É o humano do outro lado quando o agente não resolve: incidente, liberação, exceção comercial. - Vincule o grupo à sua chave — um admin do grupo manda
/vincular. Se esse admin já criou a chave no privado do bot, é 1 toque; se não, pegue o código no privado com/chavee mande/vincular lk…no grupo.
/vincular. Confira
com /chave: se responder com o nome do seu projeto, está ligado.lun_… no grupo. Ela dá acesso à conta
inteira e aparece uma única vez — guardamos apenas o hash. O código de vínculo lk… pode
circular no grupo; a chave, não. Se ela vazar, crie outra e desative a antiga.Faz tudo por chat — o agente IA
Além da API REST, a Lunium te dá um agente de verdade no Telegram. No privado — ou mencionando ele no grupo do seu projeto — você cobra, saca e paga escrevendo em português. Ele lê os dados reais da sua conta, monta a operação e executa com um toque de confirmação. Dinheiro nunca se move sem o seu OK.
R$ 250,00 → m•••@empresa.com (email)
Sai da sua movimentação do dia.✅ Confirmar PIX de R$ 250,00
po_a1b2… ✓"cobra R$ 50 do CPF X em USDC", "saca 25 USDT pra minha chave PIX", "cadê a cobrança ci_…?". O agente entende e executa — sem você lembrar de comando nenhum.
Cobrança, saque e pagamento são propostos pelo agente e só acontecem quando você toca no botão de confirmar. E só quem pediu consegue confirmar.
Adicione o agente ao grupo do projeto: todo o time recebe cada PIX em tempo real e qualquer um pode operar (com confirmação) mencionando o agente.
Dúvida de integração, erro 401, exemplo em Node? Ele responde na hora — e sabe seu tier, seu uso e o que já está configurado.
Crie sua chave agora
Direto daqui da doc. A chave aparece uma única vez — guarde na hora.
Autenticação
Toda chamada leva sua chave no header X-API-Key. A mesma chave autoriza
cash-in, cash-out, catálogo e conta.
curl https://api.luniumpay.com/keys/me \
-H "X-API-Key: lun_a1b2c3…"
Criar chave · self-service
Cria uma chave nova com cash-in e cash-out já habilitados. Sem autenticação — é o ponto de partida. Limitado por IP (5/dia).
| Campo | Tipo | Descrição |
|---|---|---|
name obrigatório | string | Nome do seu negócio (2–80 chars). Identifica a chave. |
email | string | Contato para avisos operacionais. |
settlement_address | string | Endereço Polygon 0x… fixo para liquidação do cash-in. Sem ele, cada cobrança informa o seu payout_address. |
webhook_url | string | URL https para eventos. Gera um webhook_secret (também mostrado uma única vez). |
curl -X POST https://api.luniumpay.com/keys \
-H "Content-Type: application/json" \
-d '{
"name": "Loja Exemplo",
"email": "dev@lojaexemplo.com",
"webhook_url": "https://lojaexemplo.com/webhooks/lunium"
}'const r = await fetch('https://api.luniumpay.com/keys', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Loja Exemplo',
email: 'dev@lojaexemplo.com',
webhook_url: 'https://lojaexemplo.com/webhooks/lunium',
}),
});
const conta = await r.json();
// conta.api_key ← guarde AGORA (só aparece aqui)import requests
r = requests.post("https://api.luniumpay.com/keys", json={
"name": "Loja Exemplo",
"email": "dev@lojaexemplo.com",
"webhook_url": "https://lojaexemplo.com/webhooks/lunium",
})
conta = r.json()
# conta["api_key"] ← guarde AGORA (só aparece aqui)Resposta 201:
{
"api_key": "lun_9f2c40d1e8…", // ← única vez que aparece
"webhook_secret": "whk_5d1a…", // se enviou webhook_url
"key": {
"key_id": 7, "prefix": "lun_9f2c40d1", "name": "Loja Exemplo",
"cashin_enabled": true, "cashout_enabled": true,
"fee_bps": 250, "fee_fixed_cents": 100, // padrão da casa: 2,5% + R$ 1,00 — negociável por volume
"rate_limit_per_minute": 60, …
},
"limits": { "tier": "Inicio", "daily_limit_cents": 500000, … },
"monitor_url": "https://api.luniumpay.com/cashin/monitor?token=dsh_…", // painel ao vivo só-leitura
"telegram": {
"bot": "@…",
"link": "https://t.me/…?start=lk1a2b3c…", // 1 toque = avisos em tempo real
"hint": "Crie um grupo com o time e adicione o agente."
},
"docs": "https://docs.luniumpay.com"
}
/monitor. JSON equivalente: GET /cashin/events?token=… (ou com X-API-Key).Sua conta
Tudo sobre a sua chave: configuração, tier, limite diário (cash-in e payouts — a venda não tem), uso de hoje e vínculo do Telegram.
curl https://api.luniumpay.com/keys/me -H "X-API-Key: $LUNIUM_KEY"
{
"key": { "key_id": 7, "prefix": "lun_9f2c40d1", "name": "Loja Exemplo",
"cashin_enabled": true, "cashout_enabled": true,
"webhook_configured": true, "telegram_linked": true, … },
"limits": {
"tier": "Crescimento",
"daily_limit_cents": 2500000,
"used_today_cents": 431000,
"available_today_cents": 2069000,
"settled_volume_cents": 9174350,
"next_tier": { "tier": "Escala", "unlocks_at_settled_volume_cents": 50000000,
"daily_limit_cents": 10000000 }
}
}
Atualiza name, settlement_address e webhook_url
(enviar null limpa o campo). "rotate_webhook_secret": true gira o segredo —
o novo vem na resposta, uma única vez.
curl -X PATCH https://api.luniumpay.com/keys/me \
-H "X-API-Key: $LUNIUM_KEY" -H "Content-Type: application/json" \
-d '{ "settlement_address": "0xAbC123…", "webhook_url": "https://lojaexemplo.com/wh" }'
Limites & tiers — progressivos por movimentação
A confiança é construída pelo volume. Toda chave nasce no tier Início e sobe automaticamente conforme liquida volume conosco. O pagador final é validado pelo nosso provedor PIX regulado — o seu teto é só uma régua de risco.
| Tier | Desbloqueia com | Teto diário (cash-in + payouts) |
|---|---|---|
| 🌱 Início | imediato, ao criar a chave | R$ 5.000/dia |
| 📈 Crescimento | R$ 50.000 liquidados (acumulado) | R$ 25.000/dia |
| 🚀 Escala | R$ 500.000 liquidados (acumulado) | R$ 100.000/dia |
| 🏛 Enterprise | fale com a gente | sob medida |
- O teto diário conta cash-in + payouts e renova à meia-noite (America/Sao_Paulo). O cash-out não entra nessa cota — ele tem limite por operação, não por dia.
- Fonte de verdade: os números desta página são referência; o que a API aplica vem em
GET /keys/me→limits.per_operation(faixa por operação de cash-in, cash-out e payout),limits.payer_limits(escada por CPF/CNPJ) elimits.daily_limit_applies_to(o que consome a cota). Não fixe limite no seu código: leia daqui, que muda sem aviso. - Operações em andamento contam no teto; expiradas/devolvidas liberam o espaço.
- Estourou? A API responde
403comlimitsno corpo — mostre pro seu financeiro e tente no dia seguinte, ou acelere o upgrade liquidando volume. - Seu tier atual, uso e próximo degrau:
GET /keys/meou/limitesno bot.
Limite por pagador (CPF/CNPJ) NOVO
Além do teto da sua chave, existe uma segunda régua: a de cada CPF/CNPJ que paga. Ela protege você e a gente do mesmo problema — conta laranja, cartão/PIX roubado e teste de fraude entram sempre por um pagador novo, nunca por um recorrente. O CPF novo começa curto e cresce sozinho:
| Momento do pagador | Limite |
|---|---|
| Primeira operação (nunca pagou) | R$ 60 nessa operação |
| Primeiras 24h após o 1º pagamento confirmado | R$ 200 acumulados |
| Depois de 24h do 1º pagamento | R$ 6.000 por dia |
- O relógio começa no primeiro pagamento confirmado, não na primeira cobrança criada — gerar QR é grátis, pagar não. Assim ninguém "envelhece" um CPF só emitindo cobrança.
- O limite conta o que foi pago e o que está em voo (cobrança pendente ainda válida) —
disponível = teto − pago − pendente. Vale desde a estreia: antes do 1º pagamento confirmado, o CPF/CNPJ pode ter no máximo R$ 60 em cobranças abertas ao mesmo tempo (agregado, não R$ 60 por QR) — abrir vários QRs não fura o teto. Expiradas não contam. - Pagadores que já têm histórico com você já entram maduros — nada foi resetado.
- Vale nos dois modos de uso e é independente do seu tier: mesmo no Escala, um CPF estreando paga no máximo R$ 60 na primeira.
403 com
erro: "limite_do_pagador", acao: "corrigir" e um objeto
payer_limits dizendo em que estágio o pagador está, quanto já usou, quanto resta e em
quantas horas o teto sobe — dá para mostrar na sua tela sem adivinhar. Ramifique por
erro (o contrato de toda a API); o campo code ainda vem, mas é um
alias legado com desligamento marcado para 09/11/2026 — não construa em cima dele.{
"erro": "limite_do_pagador", // ← ramifique por aqui (contrato de toda a API)
"acao": "corrigir",
"detail": "Nas primeiras 24h o CPF/CNPJ pode movimentar R$ 200,00. Já usou R$ 120,00; disponível: R$ 80,00…",
"payer_limits": {
"stage": "first_24h",
"window_limit_cents": 20000,
"used_cents": 12000,
"available_cents": 8000,
"matures_in_hours": 23
},
"code": "payer_limit" // alias legado — sai do ar em 09/11/2026, ramifique por "erro"
}
Referência rápida
Todos os endpoints numa tabela. Base: https://api.luniumpay.com · autenticação por
X-API-Key exceto onde indicado.
| Método | Caminho | Auth | Função |
|---|---|---|---|
| POST | /keys | — (5/dia/IP) | Criar chave (self-service; aparece 1×) |
| GET | /keys/me | chave | Config, tier, limites, uso do dia |
| PATCH | /keys/me | chave | name, settlement_address, webhook_url, rotate_webhook_secret |
| POST | /cashin/preview | chave | Cotação BRL→USDT (read-only) |
| POST | /cashin/charge | chave | Cobrança PIX → USDT/USDC (QR copia-e-cola) |
| GET | /cashin/{id}/status | chave | Estado + tx on-chain |
| GET | /cashin/charges | chave | Lista (status, start, end, limit) |
| GET | /catalog · /catalog/dex | chave | Ativos e redes do cash-out (USDT/USDC nas principais) |
| POST | /cash-outs | chave | Cotação cripto → PIX |
| POST | /cash-outs/{id}/accept | chave | Trava a cotação → endereço de depósito |
| GET | /cash-outs/{id} · /cash-outs | chave | Estado · lista |
| POST | /payouts | chave + liberação | PIX direto em BRL ao beneficiário |
| GET | /payouts/{id} · /payouts | chave + liberação | Status · lista |
| GET | /ping | — | Conectividade ({"ok":true}) |
OpenAPI · Postman & Insomnia
Todos os endpoints num arquivo só. Importe o spec e você tem uma coleção pronta — com autenticação e exemplos — pra testar a API sem escrever nada.
Spec OpenAPI 3.1 — 22 operações, webhooks assinados e todos os schemas de resposta, fiel à API em produção.
https://docs.luniumpay.com/openapi.json
- Import (topo à esquerda) → aba Link.
- Cole
https://docs.luniumpay.com/openapi.json→ Continue → Import. - Na coleção, defina a variável
apiKeycom a sualun_…e dispare qualquer request.
- Create → Import From → URL.
- Cole a mesma URL do spec e confirme.
- Defina o header
X-API-Keyno environment e teste.
openapi-generator pra esse mesmo arquivo.Cash-in — PIX entra, USDT sai
Seu cliente paga um PIX comum (QR copia-e-cola); a Lunium recebe, converte e envia USDT (Polygon) pro endereço que você indicar — o seu, fixo na chave, ou um por cobrança. Sem carteira conectada, sem fricção pro pagador.
amount_cents: 25000 = R$ 250,00.
Limites por cobrança: R$ 1,00 a R$ 5.000,00 · QR expira em 15 minutos.
Além destes tetos, cada CPF/CNPJ pagador tem régua própria (limite por
pagador): um pagador estreante começa em R$ 60 por cobrança.Saldo em reais (custódia) — depositar e sacar por PIX
Chaves com custódia habilitada (custodia_ativa: true em GET /saldo) guardam reais para os clientes finais do parceiro — a aba "Depositar e Sacar" de um app de carteira. O livro-razão é append-only: saldo = soma dos movimentos.
| Passo | Chamada | O que acontece |
|---|---|---|
| Depositar | POST /cashin/charge com destino: "saldo" + customer_ref | PIX pago → crédito integral na sub-conta (deposito_fee_bps = 0), bloqueado até disponivel_em (D+1 por padrão). Webhook cashin.settled com asset: "brl". Nenhuma cripto sai. |
| Consultar | GET /saldo?customer_ref=… · GET /saldo/extrato | disponivel_cents, bloqueado_cents, proximas_liberacoes, e as taxas que a tela precisa mostrar (saque_fee_bps, saque_taxa_estimada_cents). |
| Sacar em cripto | POST /saldo/sacar-cripto {amount_cents, chain, asset, payout_address, tax_number, customer_ref} | Debita valor + taxa da casa (saque_cripto_fee_bps) e entrega a cripto na carteira do cliente pelo trilho da compra (qualquer moeda/rede do catálogo, exceto Liquid). Resposta = cobrança já paid; acompanhe settlement_status em GET /cashin/{id}/status. |
| Sacar | POST /payouts com o mesmo customer_ref | Debita valor + taxa da casa (saque_fee_bps, hoje 1,8%) + taxa do banco (~R$ 1,00) antes de o PIX sair (fee_cents = total); recusa estorna. 402 saldo_insuficiente se disponível < valor + taxa. A chave PIX tem que pertencer ao tax_number (regra do banco). PIX cai em até 24h. |
curl -X POST https://api.luniumpay.com/cashin/charge -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"amount_cents":10000,"payer_tax_number":"12345678909","destino":"saldo","customer_ref":"user_42","chain":"polygon","asset":"usdt"}'
curl "https://api.luniumpay.com/saldo?customer_ref=user_42" -H "X-API-Key: $KEY"
curl -X POST https://api.luniumpay.com/payouts -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"amount_cents":5000,"pix_key":"12345678909","pix_key_type":"cpf","tax_number":"12345678909","customer_ref":"user_42","external_id":"saque-1"}'
Duas carências: o saldo aparece na hora, mas cada depósito só pode ser usado depois de carencia_horas (D+1); e o primeiro depósito de uma sub-conta trava qualquer saque por 24h (carencia_ate em GET /saldo; 423 carencia_primeiro_deposito nas duas rotas de saque). As cobranças trazem fonte (pix|saldo) e destino (cripto|saldo).
Nada é adiantado e nenhuma taxa é absorvida: o que o provedor cobra é o que o saldo paga. Custódia é habilitada por chave pelo suporte.
Cotação (read-only)
Quanto da stablecoin sai por um valor em BRL, sem criar nada. Já aplica a taxa da sua chave. Aceita asset: "usdt" (padrão) ou "usdc".
curl -X POST https://api.luniumpay.com/cashin/preview \
-H "X-API-Key: $LUNIUM_KEY" -H "Content-Type: application/json" \
-d '{ "amount_cents": 25000 }'const quote = await lunium('POST', '/cashin/preview', { amount_cents: 25000 });quote = lunium("POST", "/cashin/preview", {"amount_cents": 25000}){ "asset": "usdt", "amount_cents": 25000,
"admin_fee_bps": 250, "admin_fee_fixed_cents": 100, // 2,5% + R$1 fixo
"fee_total_cents": 723, "net_cents": 24277, // taxa total e valor líquido
"brl_per_usdt": "5.10", "usdt_amount": "47.558132" }
fee_total_cents, net_cents e usdt_amount retornados pela
API são a fonte de verdade — use-os para exibir, cobrar e conciliar. Reconstruir a conta
localmente é o caminho mais curto para uma divergência de caixa.
Um exemplo do porquê, com os números reais acima:
brl_per_usdt é a cotação
arredondada em 2 casas para leitura ("5.10"), enquanto o
usdt_amount é calculado com a cotação cheia (≈ 5,1047). Se você fizer
net_cents ÷ brl_per_usdt vai chegar a 47,601960 em vez de
47,558132 — 0,09% a mais de cripto do que vai receber, sempre para o mesmo lado.
Em volume, isso vira diferença de caixa todo mês.admin_fee_fixed_cents e admin_fee_bps na própria resposta e em
GET /keys/me — não fixe 2,5% no código, ela é negociável por volume
(contato@luniumpay.com).
Metodologia (para você entender a conta, não para reconciliar dinheiro):
base = max(0, amount_cents − admin_fee_fixed_cents) // a fixa sai primeiro
net_cents = floor(base × (10000 − admin_fee_bps) ÷ 10000) // trunca, nunca arredonda
fee_total_cents = amount_cents − net_cents // fixa + % juntas
Conferindo com o exemplo: floor((25000 − 100) × 9750 ÷ 10000) = 24277, e a taxa é
25000 − 24277 = 723. O usdt_amount já vem líquido — é exatamente o
que o destino recebe.brl_per_usdt e usdt_amount mantêm o nome por compatibilidade (política aditiva) — leia como "BRL por unidade" e "quantidade da stablecoin".Criar cobrança
| Campo | Tipo | Descrição |
|---|---|---|
amount_cents obrigatório | int | Valor do PIX em centavos. |
payer_tax_number obrigatório | string | CPF (11 dígitos) ou CNPJ (14) de quem paga. |
payout_address | string | Endereço Polygon 0x… que recebe. Opcional se a chave tem settlement_address fixo. |
asset | string | "usdt" (padrão) ou "usdc" — a stablecoin entregue. USDC é o nativo da Circle na Polygon (não o bridged USDC.e). |
payer_name | string | Nome do pagador (recomendado). |
external_id | string | Seu id do pedido — dá idempotência. |
chain | string | Hoje: polygon (padrão). |
curl -X POST https://api.luniumpay.com/cashin/charge \
-H "X-API-Key: $LUNIUM_KEY" -H "Content-Type: application/json" \
-d '{
"amount_cents": 25000,
"payer_tax_number": "12345678901",
"payer_name": "Maria Souza",
"payout_address": "0xAbC123…",
"external_id": "pedido-8812"
}'const charge = await lunium('POST', '/cashin/charge', {
amount_cents: 25000,
payer_tax_number: '12345678901',
payer_name: 'Maria Souza',
payout_address: '0xAbC123…',
external_id: 'pedido-8812',
});
mostrarQR(charge.qr_copypaste); // já vem pronto pra telacharge = lunium("POST", "/cashin/charge", {
"amount_cents": 25000,
"payer_tax_number": "12345678901",
"payer_name": "Maria Souza",
"payout_address": "0xAbC123…",
"external_id": "pedido-8812",
})
mostrar_qr(charge["qr_copypaste"]){
"cashin_id": "ci_b3f9d2a41c6e8f5a90d7",
"status": "pending",
"amount_cents": 25000,
"payout_address": "0xAbC123…", "chain": "polygon",
"qr_copypaste": "00020126580014br.gov.bcb.pix…", // PIX copia-e-cola
"qr_image_url": "https://…/qr.png",
"external_id": "pedido-8812",
"expires_at": "2026-07-20T18:45:00.000Z"
}
Status da cobrança
Snapshot completo — inclui o hash on-chain quando o USDT sai.
cashin.paid / cashin.settled), que chega antes de qualquer polling. No
polling, use 1 consulta a cada 2–3 s: em 1×/s você sozinho consome os 60 req/min da chave e
toma 429 nas outras chamadas.curl https://api.luniumpay.com/cashin/ci_b3f9d2a41c6e8f5a90d7/status \
-H "X-API-Key: $LUNIUM_KEY"
{
"cashin_id": "ci_b3f9d2a41c6e8f5a90d7",
"status": "paid",
"settlement_status": "sent",
"amount_cents": 25000,
"depix_received_cents": 25000, // o que REALMENTE entrou
"usdt_amount": "46.040515",
"settlement_tx_hash": "0x8ef1…",
"settlement_tx_url": "https://polygonscan.com/tx/0x8ef1…",
"payer_tax_number": "•••••••8901", // sempre mascarado
"paid_at": "2026-07-20T18:34:12.000Z",
"settled_at": "2026-07-20T18:34:40.000Z", …
}
Listar cobranças
As cobranças da sua chave, mais recentes primeiro. Filtros: status,
start/end, limit (máx. 200) e external_id
(recuperação). Resposta: { "charges": [ … ] } no mesmo
formato do status — perfeito pra conciliação diária.
criado_em), não pela data do pagamento.
start e end são ambos inclusivos, em ISO 8601; sem offset, a data é
lida em UTC. Data sem hora vira meia-noite UTC — então, para cobrir o dia 21 inteiro,
use start=2026-07-21&end=2026-07-22 (end no dia seguinte) ou
end=2026-07-21T23:59:59Z.Ciclo de vida do cash-in
pending↓paid↓Dois campos, não um. status
é o PIX (chega a paid e para aí); settlement_status é a entrega da cripto
(pending → sending → sent). status nunca vira "settled" — o
sucesso é status: "paid" com settlement_status: "sent", e
cashin.settled é o nome do evento, não um valor de status. Se sua
máquina de estados espera status: "settled", ela trava esperando algo que não vem. Se ficar incerto, a operação reconcilia on-chain antes de qualquer novo envio: a transação é procurada na rede pelo hash gravado, e só reenviamos quando a busca prova que nada saiu.
| status | Significa |
|---|---|
| pending | QR emitido, aguardando pagamento. |
| under_review | PIX em análise no provedor (raro, minutos). |
| paid | Dinheiro realmente recebido — a liquidação dispara sozinha. |
| expired | Ninguém pagou em 15 min. Crie outra. |
| refunded | PIX devolvido ao pagador. |
| failed | Falhou no provedor antes de pagar. |
| settlement_status | Significa |
|---|---|
| pending | Aguardando (ou reprocessando) o envio do USDT. |
| sending | Transação on-chain em andamento. |
| sent | USDT entregue — veja settlement_tx_hash. |
| incerto | Envio ambíguo; nossa operação reconcilia on-chain (nunca reenviamos às cegas: a reconciliação on-chain decide se houve envio antes de qualquer retentativa). |
| failed | Envio falhou; tratamento manual já acionado. |
cashin.settled
(ou settlement_status = "sent") — nunca pelo paid sozinho, e nunca pelo valor
cotado: o campo que vale é depix_received_cents.Cash-out — cripto entra, PIX sai
O caminho de volta: você (ou seu cliente) envia cripto e uma chave PIX recebe reais. Fluxo em 3 passos: cotar → aceitar → depositar. Depois do depósito, é conosco: conversão e PIX saem sozinhos, com evento a cada transição.
Aceitamos USDT e USDC nas principais redes — Polygon com liquidação em segundos e Ethereum, BNB Chain, Arbitrum, Optimism, Base, Avalanche, TON, Aptos, NEAR e outras com o prazo da própria rede. Veja os dois caminhos e a tabela de prazos antes de escolher a rede.
expires_at da resposta, e DEPOSIT_DETECTED só existe no rail de conversão —
na Polygon a ordem vai direto de AWAITING_DEPOSIT para processamento.Catálogo de ativos
Ativos, redes e limites correntes pro cash-out. É a fonte da verdade — o catálogo muda sozinho conforme redes entram e saem de manutenção, então leia dele em vez de fixar uma lista no seu código.
{
"updatedAt": "2026-07-31T03:18:16.793Z",
"fast": [ { "asset": "USDT", "network": "polygon" } ], // liquidação em segundos
"convert": [ // principais redes, prazo pela rede
{ "asset": "USDT", "networks": ["eth", "bsc", "arbitrum", …],
"min": "10", "max": "200000", "precision": 8 }
],
"dex": { "chains": ["polygon"], "note": "…" }
}
network: "solana" é recusado na cotação com HTTP 400 e não aparece no catálogo —
de propósito: é melhor recusar na porta do que aceitar um depósito que não vira PIX.
Redes que estão no catálogo são as que pagamos.curl https://api.luniumpay.com/catalog -H "X-API-Key: $LUNIUM_KEY"
Dois caminhos de liquidação — escolha pelo prazo
O mesmo POST /cash-outs atende os dois; quem decide é o par
asset + network que você mandar. A diferença que importa pro seu
cliente é quando o PIX cai.
| Caminho | Redes | Prazo do PIX | Quando usar |
|---|---|---|---|
fastliquidação nossa |
Polygon — USDT direto · USDC via swap on-chain | segundos após a confirmação | Padrão. É a experiência instantânea — use sempre que seu cliente puder enviar em Polygon.
O campo fast do /catalog lista só USDT; USDC-Polygon
passa por uma conversão antes e, em caso de falta de liquidez, vira revisão manual. |
convertvia exchange parceira |
USDT e USDC nas principais redes (tabela abaixo) | de ~1 min a algumas horas, conforme a rede | Quando o cliente já tem o saldo em outra rede e não quer pagar ponte. |
convert.
O tempo não é nosso: cada rede exige um número de confirmações antes de o saldo ser liberado,
e é isso que manda no prazo. Mostre a estimativa da tabela abaixo antes de o cliente
enviar a cripto — é a diferença entre uma espera compreendida e um chamado de suporte.Redes e prazos estimados
Estimativas medidas pelas confirmações exigidas por rede. O min é por ordem
(USDT 10 · USDC 20); o teto é 200.000 por ordem, respeitando o seu tier.
| Rede | network | Moedas | PIX sai em |
|---|---|---|---|
| Polygon | polygon | USDT · USDC | segundos (rail próprio) |
| TON | ton | USDT | ~1 min |
| Aptos | aptos | USDT · USDC | ~1 min |
| Avalanche | avalanche | USDT · USDC | ~2 min |
| NEAR | near | USDT · USDC | ~2 min |
| BNB Chain | bsc | USDT · USDC | ~3 min |
| Polkadot | polkadot | USDT · USDC | ~4 min |
| Base | base | USDC | ~5 min |
| Ethereum | eth | USDT · USDC | ~20 min |
| Arbitrum | arbitrum | USDT · USDC | ~21 min |
| Optimism | optimism | USDT · USDC | ~1 hora |
| Celo | celo | USDT | ~3 horas |
Também disponíveis no convert: Klaytn (klay), Plasma (plasma),
Conflux (cfxevm) para USDT; XDC (xdc), Sonic (sonic), Sei (seievm),
Sui (sui), Starknet (stark) e Algorand (algo) para USDC.
Consulte sempre GET /catalog — a lista viva pode ter mais. | |||
Tokens long-tail via DEX (Polygon). Símbolos se repetem — ao usar um token do
/catalog/dex, mande também o token_address exato na cotação.
O token é convertido on-chain antes do PIX, então o desfecho depende da liquidez do par.
Criar cotação
| Campo | Tipo | Descrição |
|---|---|---|
asset obrigatório | string | Ex.: USDT (veja /catalog). |
network obrigatório | string | polygon (segundos) ou uma das principais redes — eth, bsc, arbitrum, optimism, base, avalanche, ton, aptos, near… Prazo por rede na tabela; lista viva em /catalog. |
amount um dos dois | string | Quantidade em string decimal — "50", nunca float. Informe amount ou brl_amount. |
brl_amount um dos dois | string | Cotação reversa: o valor do PIX em reais ("250.00", até 2 casas) e a Lunium calcula a cripto — o amount da resposta é o que o cliente deposita. É o caminho natural de quem pensa em reais ("quero receber R$ 500"). |
pix_key obrigatório | string | A chave PIX que recebe os reais. Formatos aceitos (a API normaliza para o formato exato que o liquidante exige e recusa antes de existir depósito, 400 pix_key_invalida): CPF 11 dígitos · CNPJ 14 dígitos · telefone internacional +55DDDNÚMERO (+5548996005588) · e-mail · chave aleatória em UUID (6602ede6-b1a9-4e63-9178-c6883fd0095e). |
pix_key_type opcional | string | cpf · cnpj · phone · email · random. Para vender, basta a chave PIX — o tipo é deduzido dela para e-mail, CNPJ, chave aleatória e telefone com +55. Só é exigido quando a chave são 11 dígitos puros, porque CPF e telefone têm o mesmo tamanho e chutar pagaria a pessoa errada. Se você sabe o tipo, mande — explícito sempre vence a dedução. |
token_address | string | Endereço/mint exato (necessário pra tokens do /catalog/dex). |
external_id | string | Seu id — idempotência. |
refund_address informe sempre | string | Carteira de retorno do seu cliente, na rede da venda. É para onde a cripto volta em qualquer devolução (provedor recusou a chave, reversão, falha antes do envio). Sem ela a devolução vai para a origem on-chain do depósito — a carteira da exchange, se o cliente sacou de uma. |
br_code cobrança | string | PIX copia e cola (BR Code) de uma cobrança com valor — para pagar um QR code em vez de uma chave. Só com USDT ou USDC na Polygon (fora disso 400 br_code_nao_suportado). O valor do QR vira o brl_amount e a chave do recebedor sai do próprio QR (merchant_name na resposta): não envie amount, brl_amount nem pix_key junto. Se o liquidante recusar o QR, a cripto volta para refund_address (REFUNDED). |
curl -X POST https://api.luniumpay.com/cash-outs \
-H "X-API-Key: $LUNIUM_KEY" -H "Content-Type: application/json" \
-d '{
"asset": "USDT", "network": "polygon", "amount": "50",
"pix_key": "12345678901", "pix_key_type": "cpf",
"external_id": "saque-2207"
}'const ordem = await lunium('POST', '/cash-outs', {
asset: 'USDT', network: 'polygon', amount: '50',
pix_key: '12345678901', pix_key_type: 'cpf',
external_id: 'saque-2207',
});ordem = lunium("POST", "/cash-outs", {
"asset": "USDT", "network": "polygon", "amount": "50",
"pix_key": "12345678901", "pix_key_type": "cpf",
"external_id": "saque-2207",
}){
"cashout_id": "dede940b-6adb-4104-ac6b-979b1e84b1f5",
"state": "QUOTE_CREATED",
"asset": "USDT", "network": "polygon", "amount": "50",
"brl_amount": "268.00", // o PIX que a chave vai receber
"pix_key": "123•••••901", // sempre mascarada
"expires_at": "2026-07-20T18:40:00.000Z"
}
expires_at — leia dele, nunca chute.
Hoje: 15 min na Polygon; 90 min (ativo volátil) ou 300 min (stablecoin) nas demais
redes, porque lá o depósito ainda precisa das confirmações da rede. Não aceitou a tempo? Expira
sozinha, sem custo — é só cotar de novo.Aceitar a cotação
Trava a cotação e devolve o endereço de depósito. Envie exatamente a quantidade
cotada, na rede certa; em redes com memo/tag, inclua o deposit_tag.
deposit_tag está no contrato, mas ainda não é preenchido — e um depósito sem memo se perde na exchange. Por isso, enquanto for assim, as redes que exigem memo/tag (TON, XRP, XLM, ATOM, EOS, ALGO, HBAR e outras) não aparecem em GET /catalog e são recusadas no POST /cash-outs com rede_indisponivel. Você não precisa mantê-las numa lista de evitar: se está no catálogo, pode depositar; se não está, não pode. Elas voltam sozinhas quando o memo passar a vir preenchido.{
"cashout_id": "dede940b-6adb-4104-ac6b-979b1e84b1f5",
"state": "AWAITING_DEPOSIT",
"deposit_address": "0xDdE987…", // mande a cripto pra cá
"brl_amount": "268.00", …
}
from on-chain costuma ser carteira de exchange, bridge ou contrato,
que não recebe devolução com segurança (some ou cai na conta errada). Por isso:
- Deposite de uma carteira que você controla (self-custody), não direto de uma corretora. Assim existe um destino seguro caso precise devolver.
- A devolução é coordenada com a operação (hoje, manualmente) para um endereço externo que você confirma — nunca um endereço nosso, nunca a origem às cegas.
- Garantia do motor: a ordem só vira
REFUNDEDdepois de um saque on-chain bem-sucedido. Enquanto não houver destino válido, ela fica emMANUAL_REVIEWcom a cripto na nossa custódia — nada é enviado para o lugar errado e nada some. Dinheiro parado tem conserto; dinheiro no endereço errado não tem.
Status & lista
Estado atual. Nosso worker também acompanha cada ordem 24/7 e dispara webhook/Telegram a cada transição — o polling é opcional, não obrigatório.
Ordens da sua chave, mais recentes primeiro. Resposta: { "cash_outs": [ … ] }.
Ciclo de vida do cash-out
AWAITING_DEPOSIT↓Os estados internos DEPOSIT_CONFIRMED · SELLING · SOLD · FORWARDING · PAYING_OUT colapsam num único evento cashout.processing — você não precisa tratar cada um.
| state | Significa |
|---|---|
| QUOTE_CREATED | Cotação aberta; aceite pra travar. |
| AWAITING_DEPOSIT | Aguardando sua cripto no endereço informado. |
| DEPOSIT_DETECTED | Depósito visto on-chain; confirmando. |
| DEPOSIT_CONFIRMED · SELLING · SOLD · FORWARDING · PAYING_OUT | Processando (conversão e PIX). Pro seu sistema, tudo isso é o evento único cashout.processing. |
| MANUAL_REVIEW | Checagem manual rápida da operação. |
| COMPLETED | PIX pago. 🎉 |
| REFUNDING_CRYPTO | Algo impediu o PIX e a devolução está a caminho de refund_address (ou da origem do depósito, se você não informou). |
| REFUNDED | Cripto devolvida — refund_tx_hash é a prova on-chain. Só é marcado depois da transação confirmada, nunca no papel. Veja o que acontece em cada falha. |
| EXPIRED | Cotação/depósito fora do prazo. |
| FAILED | Falha terminal — nossa operação já foi acionada. |
Devolução — o que acontece em cada falha
Mande refund_address (a carteira do seu cliente, na rede da venda) em toda cotação. É o destino de qualquer devolução; sem ela, a cripto volta para a origem on-chain do depósito — e origem de saque de exchange é a carteira da exchange.
- Copia e cola no campo errado ou fora de USDT/USDC Polygon → recusado na cotação (
pix_key_invalida/br_code_nao_suportado). Nada se move: ainda não existe endereço de depósito. Para pagar um QR usebr_code. - QR recusado pelo liquidante na hora de pagar (cobrança vencida, já paga, QR não reconhecido) → o liquidante devolve a cripto para a custódia da Lunium em segundos e ela segue para
refund_address;MANUAL_REVIEW→REFUNDEDcomrefund_tx_hash. - Provedor recusa a chave depois do depósito (inválida/inexistente) →
MANUAL_REVIEW(cashout.under_review); a cripto volta à custódia da Lunium e vai automaticamente pararefund_address; terminaREFUNDED(cashout.refunded) comrefund_tx_hash. Devolve-se o líquido que o provedor devolveu. - Falha antes do envio (provedor fora do ar, chave rejeitada na submissão) → retentado a cada 5 min por até 45 min; se não liquidar, o valor cheio (taxa inclusive) volta para
refund_address;REFUNDED+ hash. - PIX revertido depois de pago (raro — conta destino bloqueada) → mesmo caminho:
REFUNDEDcomrefund_tx_hash. - Depósito diferente do cotado → paga-se o valor realmente recebido. Token diferente fica em
MANUAL_REVIEW. - Depósito depois de
expires_at→ a ordem é revivida quando o depósito aparece (até 7 dias); senãoMANUAL_REVIEW. - Teto automático: 5.000 USDT por ordem; acima, uma pessoa da operação executa e você recebe
REFUNDED+ hash do mesmo jeito. A devolução sai sempre na rede e no token do depósito, do endereço de custódia da Lunium; o gas é nosso.
refund_address, refund_tx_hash e deposit_from vêm em GET /cash-outs/{id} e em todo webhook.
Depósitos incorretos e recuperação
Depois do accept, mande exatamente a quantidade cotada, no ativo e na
rede cotados, em uma única transação, antes do expires_at. O motor
tem uma faixa de tolerância — 90% a 105% do valor cotado — e, dentro dela, o PIX é calculado
pelo que realmente chegou, não pelo que foi cotado. Fora dela, o depósito não é
processado sozinho: fica retido e vira revisão manual. Nada é devolvido à origem on-chain
automaticamente (mesma regra do refund).
| Situação | O que acontece |
|---|---|
| Valor exato | Processa normalmente; PIX pelo valor cotado. |
| A menos, dentro de 90–100% | Processa; o PIX sai proporcional ao que chegou (menor). Você recebe pelo que de fato depositou. |
| A menos, abaixo de 90% | Não casa → MANUAL_REVIEW. Recuperação coordenada com a operação, para um endereço externo que você confirma. |
| A mais, dentro de 100–105% | Processa pelo valor cotado; o excedente não é devolvido sozinho — recupere pelo suporte. Evite pagar a mais. |
| A mais, acima de 105% | Não casa → MANUAL_REVIEW. O teto existe de propósito: impede quitar uma ordem pequena com um depósito grande (todos os depósitos do trilho de conversão caem no mesmo endereço). |
| Duas transações / depósito parcial | Cada depósito é avaliado sozinho; o que não casar na faixa vira revisão manual. Mande uma transação só, do valor exato. |
| Token errado (rede certa) | Não é o ativo esperado → não casa → revisão manual. |
| Rede errada | Não casa com a ordem → revisão manual. (Redes que exigem memo/tag estão bloqueadas justamente porque o depósito se perderia — veja Aceitar a cotação.) |
Depois do expires_at | A ordem já expirou; o depósito chega órfão e vira revisão manual. Sempre deposite antes do expires_at da resposta. |
Payouts — PIX direto em reais libera a R$ 1.000
O terceiro trilho da mesma chave: pagar qualquer chave PIX em BRL com um único POST —
sem cripto na ponta, sem QR, sem aceite. Você manda o valor e a chave PIX do beneficiário; o PIX
sai e você recebe o evento payout.sent (webhook + Telegram). Ideal pra plataformas que
precisam pagar usuários, fornecedores ou comissões em reais.
GET /keys/me devolve o bloco payout com
enabled, settled_volume_cents, remaining_cents e
via ("automatico" ou "manual") — leia a elegibilidade de lá, não fixe o gatilho
no código. Cash-in e cash-out continuam 100% self-service desde o primeiro minuto.Enviar um PIX
| Campo | Tipo | Descrição |
|---|---|---|
amount_cents obrigatório | int | Valor líquido que chega na chave PIX (centavos). |
pix_key obrigatório | string | A chave PIX do beneficiário. |
pix_key_type opcional | string | cpf · cnpj · phone · email · random. Para vender, basta a chave PIX — o tipo é deduzido dela para e-mail, CNPJ, chave aleatória e telefone com +55. Só é exigido quando a chave são 11 dígitos puros, porque CPF e telefone têm o mesmo tamanho e chutar pagaria a pessoa errada. Se você sabe o tipo, mande — explícito sempre vence a dedução. |
tax_number | string | CPF/CNPJ do beneficiário (recomendado — validação na ponta). |
beneficiary_name | string | Nome do beneficiário. |
external_id | string | Seu id — idempotência (retry nunca paga 2×). |
curl -X POST https://api.luniumpay.com/payouts \
-H "X-API-Key: $LUNIUM_KEY" -H "Content-Type: application/json" \
-d '{
"amount_cents": 15000,
"pix_key": "maria@exemplo.com", "pix_key_type": "email",
"tax_number": "12345678901",
"external_id": "comissao-042"
}'const payout = await lunium('POST', '/payouts', {
amount_cents: 15000,
pix_key: 'maria@exemplo.com', pix_key_type: 'email',
tax_number: '12345678901',
external_id: 'comissao-042',
});payout = lunium("POST", "/payouts", {
"amount_cents": 15000,
"pix_key": "maria@exemplo.com", "pix_key_type": "email",
"tax_number": "12345678901",
"external_id": "comissao-042",
}){
"payout_id": "po_4c1d9e22ab37f0885a1c",
"status": "processing",
"amount_cents": 15000,
"pix_key": "ma•••@exemplo.com", "pix_key_type": "email",
"external_id": "comissao-042",
"created_at": "2026-07-21T12:00:00.000Z"
}
limits.daily_limit_applies_to no
GET /keys/me: leia de lá, não desta página.Status & lista
Resposta da lista: { "payouts": [ … ] }. Os avisos chegam sozinhos por webhook e
Telegram — polling é opcional.
| status | Significa |
|---|---|
| processing | PIX em processamento no arranjo bancário. |
| sent | PIX na conta do beneficiário (evento payout.sent). |
| failed | Não foi possível pagar (chave inexistente, recusa). O campo error explica; nada foi debitado do seu teto. |
| refunded | Pagamento devolvido pelo banco do beneficiário. |
Webhooks assinados
Configure a webhook_url (na criação da chave ou via PATCH /keys/me) e receba um
POST a cada evento — cash-in e cash-out, mesma assinatura, mesmo formato:
POST {sua webhook_url}
Content-Type: application/json
X-Webhook-Signature: sha256=3f5a9c… // v1 — HMAC do corpo bruto
X-Lunium-Signature: t=1786153475,v1=8b21e4… // v2 — HMAC de "t.corpo" (prefira esta)
X-Lunium-Event: cashin.settled
X-Lunium-Event-Id: evt_9c3f21a70b5e4d8812ff0a63
X-Lunium-Delivery-Id: dlv_4f0a72c1e8b5 // muda a cada tentativa
X-Lunium-Attempt: 1 // >1 = voce ja pode ter recebido
X-Lunium-Timestamp: 1786153475
{
"event": "cashin.settled",
"event_id": "evt_9c3f21a70b5e4d8812ff0a63", // estavel entre retentativas
"created_at": "2026-07-20T18:34:41.000Z",
"data": { …mesmo formato do endpoint de status… }
}
Guarde o event_id. Ele é a identidade do evento e não muda em retentativa — é
o que garante que um retry nosso não vire crédito em dobro do seu lado. O
X-Lunium-Delivery-Id é o contrário: identifica aquela tentativa, e é o número
que você cita pra gente achar o envio exato. E trate evento desconhecido como no-op: a lista
cresce sem aviso, e ignorar o que você não conhece é o comportamento correto.
| Evento | Quando dispara |
|---|---|
cashin.paid | PIX confirmado (dinheiro em mãos). |
cashin.delayed | O provedor reteve a liberação (acontece na primeira operação de um pagador). Não é falha: vira paid sozinho. Não crie segunda cobrança. |
cashin.settled | USDT entregue — o evento pra creditar seu cliente. |
cashin.settlement_failed | Falha na liquidação (tratamento acionado). |
cashin.expired · cashin.refunded · cashin.failed | Fins sem pagamento/estorno. |
cashout.awaiting_deposit | Cotação aceita, endereço emitido. |
cashout.deposit_detected | Cripto vista on-chain. |
cashout.processing | Convertendo e preparando o PIX. |
cashout.completed | PIX pago. |
cashout.under_review · cashout.refunding · cashout.refunded · cashout.expired · cashout.failed | Desvios e fins alternativos. |
payout.sent | PIX pago ao beneficiário (payout direto). |
payout.failed · payout.refunded | Payout não pago / devolvido. |
Verifique a assinatura (sempre!)
Vão duas assinaturas, do mesmo segredo. A v1 (X-Webhook-Signature)
assina só o corpo e continua valendo — se o seu handler já a verifica, não mude nada. A v2
(X-Lunium-Signature) assina "<timestamp>.<corpo bruto>" e existe
por um motivo prático: como a v1 não tem hora dentro, ela vale para sempre — quem capturar uma
entrega (um log de proxy, um endpoint que trocou de dono) pode reenviar aquele
cashin.settled meses depois e você creditaria de novo. Com o timestamp dentro do HMAC
você recusa o que for velho. Em integrações novas, use a v2.
Status da v1: descontinuada, com data marcada — desligamento em
9 de novembro de 2026 (segunda-feira). Até lá ela continua
sendo enviada normalmente às integrações existentes e nada quebra. Chaves criadas a partir de
08/08/2026 já recebem somente a v2. Migre o handler para X-Lunium-Signature antes
da data — o lembrete sai também no grupo de parceiros e
na changelog.
import { createHmac, timingSafeEqual } from 'node:crypto';
// use o CORPO BRUTO (string/buffer), não o JSON re-serializado
// v2 — recusa entrega antiga, entao um replay nao te credita duas vezes
function assinaturaValida(corpoBruto, header, secret, toleranciaSeg = 300) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(String(header || ''));
if (!m) return false;
if (Math.abs(Date.now() / 1000 - Number(m[1])) > toleranciaSeg) return false; // velho demais
const esperada = createHmac('sha256', secret).update(`${m[1]}.${corpoBruto}`).digest('hex');
const a = Buffer.from(esperada), b = Buffer.from(m[2]);
return a.length === b.length && timingSafeEqual(a, b);
}
app.post('/webhooks/lunium', express.raw({ type: '*/*' }), (req, res) => {
if (!assinaturaValida(req.body, req.headers['x-lunium-signature'], process.env.LUNIUM_WEBHOOK_SECRET))
return res.status(401).end();
const { event, event_id, data } = JSON.parse(req.body);
if (jaProcessei(event_id)) return res.status(200).end(); // retry nosso nao credita 2x
if (event === 'cashin.settled') creditar(data.external_id, data.usdt_amount);
res.status(200).end(); // responda 2xx rápido; processe async
});import hmac, hashlib, re, time
# v2 — recusa entrega antiga, entao um replay nao te credita duas vezes
def assinatura_valida(corpo_bruto: bytes, header: str, secret: str, tolerancia=300) -> bool:
m = re.match(r"^t=(\d+),v1=([0-9a-f]{64})$", header or "")
if not m or abs(time.time() - int(m.group(1))) > tolerancia:
return False
esperada = hmac.new(secret.encode(), f"{m.group(1)}.".encode() + corpo_bruto, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperada, m.group(2))
# Flask
@app.post("/webhooks/lunium")
def lunium_webhook():
if not assinatura_valida(request.get_data(), request.headers.get("X-Lunium-Signature"), SECRET):
return "", 401
evento = request.get_json()
if ja_processei(evento["event_id"]): # retry nosso nao credita 2x
return "", 200
if evento["event"] == "cashin.settled":
creditar(evento["data"]["external_id"], evento["data"]["usdt_amount"])
return "", 200- Entrega: retry com backoff exponencial: 12 tentativas, esperando 2ⁿ minutos com teto de 60 — ~7 horas no total, não um dia. Qualquer 2xx encerra. Cada destino é tentado em paralelo: um endpoint seu fora do ar não atrasa os eventos de mais ninguém.
- Dedupe: cada par (operação, evento) dispara uma vez. Do seu lado, deduplique pelo
event_id. - Nada se perde em silêncio: esgotadas as tentativas, a entrega fica como
failedemGET /webhooks/deliveriese você a recupera com o retry abaixo. Nossa operação também é avisada na hora — antes, um evento podia morrer sem ninguém saber. - webhook_url precisa ser
httpse apontar para endereço público da internet. - Segurança: rejeite assinatura inválida com 401; nunca processe sem verificar.
Teste o seu handler antes do primeiro real
Não descubra que o endpoint estava errado quando o primeiro PIX de verdade não chegar:
curl -X POST https://api.luniumpay.com/webhooks/test \
-H "X-API-Key: $LUNIUM_KEY"
// resposta — o que o SEU endpoint respondeu, medido por nós
{ "ok": true, "status_code": 200, "ms": 180,
"event_id": "evt_…", "delivery_id": "dlv_…", "url": "https://seu.app/webhooks/lunium" }
Dispara um evento webhook.test pelo mesmo transporte da produção — mesmas
assinaturas, mesmos headers —, então o que passar aqui passa lá. Não move dinheiro, não entra na
fila e funciona também com chave de sandbox. Máximo de 6 por minuto.
O que saiu, o que falhou, e como recuperar
GET /webhooks/deliveries?status=failed // tambem: ?event=&limit=
POST /webhooks/deliveries/{event_id}/retry // reenvia agora
A lista devolve status (pending · sent · failed),
attempts, last_error e um failed_count — você não precisa saber
filtrar pra descobrir que perdeu evento. O retry tenta na hora e responde com o resultado; se falhar
de novo, a entrega volta pra fila com 12 tentativas novas. O que já foi confirmado com 2xx é recusado
com 409 ja_entregue, pra você não duplicar sem querer.
Nota de design: o retry é endereçado pelo event_id porque hoje cada
evento tem exatamente um destino (uma webhook_url por chave) — evento e entrega
são 1:1. O delivery_id identifica cada tentativa de envio. Se um dia houver
múltiplos destinos por evento, nascerá uma rota por entrega — esta continua valendo.
Agente no Telegram — operação e suporte
Cada cliente da Lunium tem um agente de verdade no Telegram: uma IA (Claude, via API, com acesso às ferramentas da conta), não um menu de botões. Ele cria credenciais, opera por linguagem natural, notifica em tempo real e tira dúvidas de integração — sempre com a sua chave, isolado por cliente (multi-tenant).
- Credencial no chat. Diga "meu nome é SuaEmpresa" (ou
/criarchave) e a chave de API nasce ali, com cash-in e cash-out liberados. Self-service, em escala — ninguém da Lunium gera credencial por você. - Opera por linguagem natural, com confirmação. Peça "cobra R$ 250 do CPF …" ou "saca 25 USDT pra minha chave PIX": o agente lê os dados reais da sua conta, monta a operação e pede um toque de confirmação antes de qualquer movimento. Dinheiro nunca se move sem o seu OK.
- Notifica em tempo real. Cada PIX que entra, cada moeda comprada e vendida (com link do Polygonscan), cada PIX que sai, revisões e falhas — no segundo em que acontecem.
- No grupo do seu projeto (recomendado pra times). Crie um grupo (ex.: Lunium SeuProjeto), adicione o agente e um admin vincula a chave: o time inteiro passa a receber os avisos e a consultar — cada grupo escopado à sua chave, com credenciais e segredos sempre reservados ao privado.
- Monitor ao vivo.
/monitorabre um painel web em tempo real do seu fluxo (cash-in, cash-out, payouts) — só-leitura e compartilhável com o time. Também nomonitor_urlda sua chave. Vazou o link?PATCH /keys/mecom{"rotate_monitor_token": true}gira o token — a URL antiga para de mostrar dados na hora e a nova vem na resposta. - Suporte 24/7. Texto livre — "como valido a assinatura do webhook?", "tô tomando 401" — e o agente responde na hora, com exemplos, treinado nesta documentação.
O link do seu agente vem na resposta do POST /keys (campo telegram.link)
e no GET /keys/me — um toque e o chat está vinculado. Os comandos abaixo funcionam também
por linguagem natural.
| Comando | O que faz |
|---|---|
"meu nome é …" · /criarchave | Cria sua chave de API no chat (aparece uma única vez) e vincula os avisos. |
/cobrar <valor> <cpf/cnpj> [0x…] [usdc] | Cobrança PIX → USDT/USDC no chat, com as mesmas guardas da API (teto, taxa da chave). |
/cotar <valor> [usdc] · /calcular <qtd> | Preview de conversão (não cria nada) e cálculo reverso do quanto cobrar. |
/qrdelay <valor> <cpf> <horas> | Cobrança com validade estendida (até 720 h) — recurso operacional exclusivo do agente; pela API REST o prazo é o expires_at padrão (~15 min). |
/sacar <qtd> <ativo> <rede> <chave_pix> <tipo> | Cash-out cripto → PIX: cotação + aceite no botão + endereço de depósito. |
/pagar <valor> <chave_pix> [tipo] | Payout PIX direto (libera a R$ 1.000 liquidados); confirma no botão. |
/status <id> · /extrato · /limites · /chave | Consultas: situação de uma operação, movimentações, tier/uso e dados da chave. |
/monitor | Link do seu painel ao vivo (só-leitura, escopado à chave) — compartilhável com o time. |
/webhook <https://url> | Configura o webhook pelo chat (segredo HMAC mostrado uma vez); /webhook off desliga. |
/vincular <código> · /ping · /desvincular | Vincula um chat/grupo à chave, testa a conectividade e silencia os avisos. |
| qualquer pergunta | Suporte de integração responde em segundos, com exemplos. |
Quem pode o quê — permissões do agente
O agente atende no seu privado (DM) e no grupo vinculado. As regras não são as mesmas nos dois — e vale entender antes de adicionar gente ao grupo:
| Ação | No privado (DM) | No grupo vinculado |
|---|---|---|
Consultar (/status, /extrato, /limites, /monitor) | você | qualquer membro |
| Iniciar cobrança, cash-out ou payout | você | qualquer membro |
| Confirmar uma operação (o toque que move dinheiro) | você | só quem iniciou aquela — ninguém confirma a do outro |
| Vincular / desvincular / trocar webhook (config) | você | só admin do grupo (creator/administrator no Telegram) |
X-API-Key — só adicione quem você
confiaria com o dinheiro. Atenção especial ao /pagar (payout: PIX da sua
tesouraria para uma chave que o iniciador escolhe — a única operação que manda dinheiro seu para
fora): qualquer membro do grupo pode iniciá-lo. Se isso for sensível no seu caso, mantenha o
payout desligado (ele já nasce assim) ou fale com a gente para restringi-lo a admins.Papéis nomeados (Owner · Admin · Operator · Viewer) com capacidades por papel estão no nosso roteiro para times maiores. Hoje o modelo é o da tabela acima: consulta e operação para o grupo, config para admin, confirmação sempre presa a quem pediu.
Chave sem passar por um humano
Se uma ferramenta respondeu erro: "chave_ausente", um agente não precisa parar e pedir credenciais. POST /keys/sandbox devolve uma chave lun_test_ numa chamada, sem autenticação e sem formulário — pelo MCP, a mesma coisa é a ferramenta lunium_create_sandbox_key, que não exige chave nenhuma.
Ela roda o cash-out na mesma URL base da produção e nada liquida (cash-in e payouts não são simulados). Deixe claro ao usuário que é chave de teste: mover dinheiro real exige chave de produção, e isso é decisão de humano.
Z — ex.: 2026-08-01T22:35:49.123Z.
Converta para o fuso do seu usuário na hora de exibir; não trate o horário como local.O que esperar, em números que medimos
São medições, não promessas — colhidas em 02/08/2026 contra o serviço no ar.
| O quê | Medido |
|---|---|
| Liquidação, da cotação ao PIX pago | 49–65 s em Polygon |
| Ordem no sandbox, ponta a ponta | p50 34 ms · p95 76 ms com 20 simultâneas |
| Vazão sustentada (sandbox) | ~200 ordens/s sem a latência de produção se mexer |
| Limite por chave | 60 req/min — um 429 é proteção, não falha |
| Por operação | R$ 6,00 a R$ 250.000,00 (confira sempre em GET /keys/me → limits.per_operation) |
GET /catalog, e dimensione o retry pelo expires_at da resposta — nunca por uma janela fixa.payout_nao_liberado e
acao: "esperar", dizendo quanto falta. Em GET /keys/me, o bloco
payout traz enabled, settled_volume_cents e
remaining_cents — leia a elegibilidade de lá em vez de fixar o gatilho no código.Erros
O contrato de erro é: erro (código estável, que não muda quando o texto muda),
acao (o que fazer) e detail (texto para humano —
nunca ramifique por ele). Erros de limite trazem também limits, já convertido na moeda da sua ordem.
Conectividade sem auth: GET /ping → {"ok":true,"msg":"pong"}.
detail.
Então ramifique nesta ordem: 1) o status HTTP (sempre correto), 2) o campo
erro quando presente, 3) nunca o texto de detail. Estamos
preenchendo erro e acao nas respostas que faltam — é adição, então seu
código não quebra quando isso chegar.{ "erro": "limite_diario",
"acao": "esperar",
"detail": "Essa cobrança (R$ 900.00) estoura seu limite diário. Disponível hoje: R$ 450.00.",
"limits": { "daily_limit_cents": 500000, "used_today_cents": 455000 } }
As quatro ações
acao | Significa |
|---|---|
| corrigir | O pedido está errado. Repetir idêntico nunca vai funcionar. |
| repetir | Falha transitória nossa. Tente de novo com backoff. |
| esperar | Uma cota renova. Volte depois — aqui tentar amanhã funciona. |
| parar | Não repita. Avise um humano. |
Essa distinção existe porque duas situações opostas devolviam a mesma resposta: uma ordem maior que a cota de um dia inteiro respondia “tente amanhã” — e amanhã falhava igual, para sempre.
Todos os códigos
Os mesmos códigos saem do sandbox e da produção. Código que ramifica por erro no
sandbox continua funcionando quando você troca a chave.
| Código | Ação | O que aconteceu |
|---|---|---|
chave_ausente | parar | Falta o header X-API-Key. POST /keys/sandbox cria uma de teste, sem cadastro. |
formato_invalido | parar | O valor não é uma chave Lunium — elas começam com lun_ ou lun_test_. |
chave_incorreta | parar | O prefixo existe mas o segredo não confere — a chave foi cortada ao copiar. O detail diz quantos caracteres vieram e quantos são esperados. |
chave_desconhecida | parar | Nenhuma chave começa com esse prefixo. Provavelmente o ambiente errado. |
campos_obrigatorios | corrigir | asset e network são obrigatórios (veja GET /catalog). |
amount_invalido | corrigir | amount tem que ser decimal positivo em string: "25.5". |
pix_key_obrigatoria | corrigir | Falta a pix_key — quem recebe os reais. |
tipo_ambiguo | corrigir | A chave tem 11 dígitos puros: CPF ou telefone. Recusamos em vez de chutar — pagar a pessoa errada é pior que um erro. Informe pix_key_type. |
pix_key_invalida | corrigir | A chave não bate com o tipo declarado. |
valor_abaixo_do_minimo | corrigir | Leia limits.min_amount — já vem convertido na moeda da sua ordem. |
valor_acima_do_maximo | corrigir | Leia limits.max_amount, mesma ideia. |
limite_diario | esperar | A cota diária do seu tier. Renova à meia-noite e cresce com o volume liquidado — é o único erro em que tentar amanhã realmente funciona. |
rede_indisponivel | corrigir | Essa rede não está liquidando agora. Use polygon (segundos) ou outra de GET /catalog. |
nao_encontrado | corrigir | O id está errado ou é de outra chave. Não repita o mesmo id. |
asset_invalido | corrigir | A moeda pedida nao esta no catalogo. Leia GET /catalog — ele muda. |
cashin_indisponivel | esperar | Compra temporariamente fora (troca de provedor PIX). O cash-out segue normal. |
cashout_indisponivel | esperar | Venda temporariamente fora. Nada foi cobrado. |
cotacao_expirada | corrigir | A cotacao venceu antes do aceite. Recote, nao insista na mesma: o preco mudou. |
cotacao_indisponivel | repetir | Nao conseguimos precificar agora (feed de preco instavel). Tente de novo com backoff. |
documento_invalido | corrigir | CPF/CNPJ com digito verificador errado. |
erro_interno | repetir | Falha inesperada nossa. Retry com backoff; se persistir, fale com o suporte. |
external_id_divergente | corrigir | Voce reusou um external_id com parametros diferentes. Devolvemos a ordem original e avisamos, em vez de deixar voce achar que atualizou algo. |
limite_de_chaves_teste | esperar | Cota de chaves de sandbox por IP no dia. Renova sozinha. |
limite_do_pagador | corrigir | O CPF/CNPJ pagador estourou a escada dele (R$ 60 na estreia, R$ 200 nas primeiras 24h, R$ 6.000/dia). E do pagador, separado do teto da chave. |
payer_tax_invalido | corrigir | O documento do pagador nao passou na validacao. |
payer_tax_number_invalido | corrigir | Numero do documento do pagador em formato invalido. |
payout_address_invalido | corrigir | O endereco nao e valido para a rede escolhida. |
payout_address_obrigatorio | corrigir | Falta o endereco de destino da cripto. |
payout_nao_liberado | esperar | Payout ainda nao habilitado para esta chave — libera por volume ja liquidado conosco. |
provedor_indisponivel | repetir | O provedor de liquidacao recusou ou nao respondeu. Transitorio — nao recrie a ordem, ela e retentada sozinha. |
sandbox_sem_cashin | parar | Chave de teste nao cria cobranca real. Use uma chave de producao ou o fluxo de sandbox. |
{ "detail": "Essa cobrança (R$ 900.00) estoura seu limite diário. Disponível hoje: R$ 450.00. …",
"limits": { "daily_limit_cents": 500000, "used_today_cents": 455000, "available_today_cents": 45000 } }
Por código HTTP
| Código | Quando | O que fazer |
|---|---|---|
| 400 | Campo faltando/inválido. | Corrija o payload — o detail diz o campo. |
| 401 | Sem chave ou chave inválida. | Confira o header X-API-Key. |
| 403 | Produto desabilitado na chave · limite diário estourado. | Veja limits; o tier sobe sozinho com volume. |
| 404 | Recurso inexistente ou de outra chave. | Ids são escopados por chave — confira o id. |
| 409 | external_id reusado com outros parâmetros. | Gere um id novo por pedido. |
| 429 | Rate limit da chave (ou criação de chave por IP). | Backoff exponencial; padrão 60 req/min. |
| 500 | Erro interno inesperado. | Retry; se persistir, suporte. |
| 502 · 503 | Instabilidade a montante (cotação, provedor PIX, orquestrador). | Retry com backoff; nada foi cobrado. |
429, 500, 502 e 503 podem ser repetidos com backoff exponencial (250 ms → 1 s → 4 s…). Em escrita (charge, cash-out, payout) sempre mande um external_id: aí qualquer retry é idempotente e nunca cobra/paga 2×. Única exceção: um 500 em POST /payouts pode significar que o PIX saiu no provedor mas falhou ao registrar — não repita às cegas, confira com o suporte.Autenticação & disponibilidade (qualquer rota)
| HTTP | detail que você recebe | Causa & correção |
|---|---|---|
401 | X-API-Key invalida ou ausente. | Chave errada/ausente. Envie X-API-Key: lun_… (crie em POST /keys). |
429 | erro: "rate_limit", acao: "esperar" | Passou do rate_limit_per_minute (padrão 60). Leia Retry-After e X-RateLimit-* na resposta; espace as chamadas com backoff. |
503 | Serviço inicializando — banco indisponível. | Reinício momentâneo do serviço. Retry em segundos. |
404 | Rota nao encontrada. | Método ou caminho errado. Confira a referência rápida. |
Chaves (/keys)
| HTTP | detail | Causa & correção |
|---|---|---|
429 | Limite de 5 chaves por dia por IP… | Máx. 5 chaves/dia por IP. Reuse a chave ou fale com a gente. |
400 | name obrigatorio (2 a 80 caracteres)… | Informe o nome do negócio (2–80 chars). |
400 | email invalido. | E-mail malformado (o campo é opcional — pode omitir). |
400 | settlement_address invalido (endereco Polygon 0x...). | Endereço EVM inválido. Use um 0x… de 42 chars. |
400 | webhook_url invalida. · webhook_url precisa ser https. | URL malformada ou http. Use https://. |
401 | X-API-Key obrigatória. | /keys/me exige a chave no header. |
400 | Nada para atualizar. Campos: name, settlement_address, webhook_url, rotate_webhook_secret. | PATCH sem nenhum campo válido no corpo. |
Cash-in (/cashin)
| HTTP | detail | Causa & correção |
|---|---|---|
403 | Cash-in nao habilitado para esta chave. | Trilho desligado na chave. Fale com o suporte. |
400 | Valor minimo: R$ 1.00. · Acima do limite (R$ 5000.00). | amount_cents fora da faixa 100–500000. |
400 | asset deve ser "usdt" ou "usdc" (Polygon). | Só usdt ou usdc. |
400 | payer_tax_number deve ser CPF (11) ou CNPJ (14) digitos. | Só dígitos, 11 ou 14. |
400 | Rede nao suportada. Use chain="polygon". | Cash-in liquida na Polygon. |
400 | payout_address obrigatorio (endereco USDT Polygon 0x...). | Informe o endereço de destino (ou defina settlement_address na chave). |
400 | payout_address invalido (endereco Polygon 0x...). | Endereço EVM inválido. |
409 | external_id ja utilizado com outros parametros. | Mesmo id, payload diferente. Gere um id novo. |
403 | Essa cobrança (…) estoura seu limite diário… (+ limits) | Teto do tier. Veja limits; sobe com volume liquidado. |
502 | Erro ao gerar PIX: … · O provedor PIX não retornou o QR… | Provedor PIX instável. Retry — nada foi cobrado. |
404 | Cobranca nao encontrada. | Id inexistente ou de outra chave. |
403 | Listagem exige X-API-Key. | /cashin/charges não existe no modo público. |
Cash-out (/cash-outs, /catalog)
| HTTP | detail | Causa & correção |
|---|---|---|
401 | Cash-out exige X-API-Key. Crie a sua em POST /keys… | Cash-out não tem modo público. Use a chave. |
403 | Cash-out não habilitado para esta chave. | Trilho desligado na chave. |
400 | asset e network são obrigatórios (veja GET /catalog). | Pegue valores válidos em /catalog. |
400 | amount deve ser decimal positivo em STRING (ex.: "25.5"). | Mande amount como string, nunca float. |
400 | pix_key obrigatória… · pix_key_type deve ser: cpf, cnpj, phone, email ou random. | Informe a chave PIX e o tipo certo. |
409 | external_id já utilizado com outros parâmetros. | Gere um id novo. |
403 | Limite diário do tier … esgotado… (+ limits) | Teto atingido. Renova à meia-noite; cresce com volume. |
503 | Cotação temporariamente indisponível (pico de demanda)… | Fila a montante cheia. Retry com backoff. |
502 | Cash-out: <erro do orquestrador> | Instabilidade a montante. Retry; nada foi movido. |
404 | Cash-out não encontrado. | Id inexistente ou de outra chave. |
Payouts (/payouts)
| HTTP | detail | Causa & correção |
|---|---|---|
403 | erro: "payout_nao_liberado", acao: "esperar" | A chave ainda não liquidou R$ 1.000,00 — a liberação é automática, sem aprovação humana, e o corpo diz exatamente quanto falta (limits.falta_cents). Acompanhe em GET /keys/me, bloco payout. |
400 | amount_cents mínimo: 100… · … acima do teto por operação (R$ 5000.00). | Faixa 100–500000 centavos. |
400 | pix_key obrigatória… · pix_key_type deve ser: … | Informe a chave PIX do beneficiário e o tipo. |
400 | tax_number deve ser CPF (11) ou CNPJ (14) dígitos. | Opcional, mas se enviar tem que ter 11 ou 14 dígitos. |
409 | external_id já utilizado com outros parâmetros. | Gere um id novo. |
403 | Esse payout (…) estoura seu limite diário… (+ limits) | Teto do tier (cash-in + payouts somam na cota; cash-out fora). O corpo limits diz o disponível, e o escopo vigente vem em daily_limit_applies_to no GET /keys/me. |
503 | Pico de demanda — tente novamente em alguns segundos. | Rate do provedor. Retry com backoff. |
502 | Payout recusado: <erro do provedor PIX> | Provedor rejeitou (chave inválida, etc.). Veja o detail. |
| 500 | Erro ao registrar o payout — NÃO repita sem conferir com o suporte. | Cuidado: o PIX pode ter saído no provedor mas falhou ao gravar aqui. Não reenvie às cegas — confira com o suporte antes. |
Rate limits
- 60 requisições/min por chave (token bucket, suave). Excedeu →
429. - POST /keys: 5 chaves/dia por IP.
- Cotações de cash-out têm um teto global de proteção; sob pico,
503— repita em segundos. - Precisa de mais? Fale com a gente — limite por chave é configurável.
Você não precisa adivinhar o orçamento. Toda resposta autenticada carrega o estado atual nos headers — leia-os em vez de bater no teto para descobrir:
X-RateLimit-Limit: 60 // seu teto por minuto (= rate_limit_per_minute)
X-RateLimit-Remaining: 42 // quantas ainda cabem agora
X-RateLimit-Reset: 18 // segundos até o balde encher de novo
No 429, vai também Retry-After: <segundos> — espere isso antes de
repetir, com backoff. O corpo traz erro: "rate_limit", acao: "esperar".
Regras que evitam erro caro
Cada uma destas já custou dinheiro — a alguém, aqui. Estão na doc principal para não custar ao seu.
Cobrança de valor fixo não aceita “quase”
Um QR de cobrança concilia por valor exato e pelo txid que vive dentro do
BR Code. Pagar R$ 0,59 a mais é tão invisível quanto R$ 0,59 a menos: o dinheiro
chega e a venda continua “não paga”. Se você repassa um copia-e-cola ao seu cliente,
mande o BR Code inteiro — não apenas a chave PIX extraída dele. A chave sozinha vira
uma transferência avulsa, sem txid, e nenhuma conciliação automática encontra.
expires_at manda no prazo, não o seu código
Leia sempre o expires_at da resposta. Hoje são ~15 minutos em Polygon e até 300
em outras redes, mas isso muda com a rede e com o provedor. Janela fixa no cliente é a origem
clássica do “paguei e expirou”.
Datas: ISO 8601 em UTC.
Toda data da API sai em ISO 8601 em UTC, terminando em Z:
2026-08-01T22:35:49.123Z. Nunca trate como hora local — converta no seu lado.
A única exceção é a cota diária da chave, que é apurada no fuso de Brasília (America/Sao_Paulo)
e por isso vira à meia-noite daqui, não à meia-noite UTC. Um horário lido errado vira
“pagamento fora do prazo” numa discussão com o lojista — e o comprovante é documento.
delayed não é falha
O PIX foi pago e o provedor está segurando a liberação — comum na primeira operação de um
pagador novo. A resposta traz delay_until. Não cancele e não crie uma segunda
cobrança: ela vira paid sozinha, e a segunda cobrança vira dinheiro em duplicidade
para conciliar na mão.
Ausência não é prova de ausência
nao_encontrado na verificação de um E2E significa que a Lunium não liquidou
aquele pagamento — não que o PIX não aconteceu. Outra instituição pode tê-lo liquidado.
Relate essa diferença ao seu usuário em vez de alegar fraude.
external_id em toda escrita
E a chave de reconciliacao e o que torna a chamada idempotente: repetir devolve a
mesma ordem em vez de criar outra. Reusar o mesmo id com parametros diferentes devolve a
ordem original e responde external_id_divergente — de proposito, para voce nao
achar que atualizou algo que nao mudou.
Idempotência
Envie o seu id de pedido em external_id (cash-in e cash-out). Repetiu a chamada
com o mesmo external_id e os mesmos parâmetros? Volta a mesma operação (200) — sem
duplicar cobrança nem ordem. Mesmo id com parâmetros diferentes → 409.
É a sua proteção contra timeout + retry.
Segurança
| Camada | Como funciona |
|---|---|
| Transporte | HTTPS/TLS em toda a superfície; HTTP não é atendido. |
| Credencial | Chave armazenada apenas como hash (irrecuperável do nosso lado); prefixo em claro só para identificação. Vazou? Crie outra na hora e nos acione para desativar a antiga. |
| Webhooks | Todo evento sai com duas assinaturas HMAC-SHA256: X-Lunium-Signature (v2, com timestamp — verifique esta e recuse entregas velhas) e X-Webhook-Signature (v1, só o corpo — deprecated; desligamento em 09/11/2026, e chaves criadas a partir de 08/08/2026 já não a recebem). Rejeite assinatura inválida com 401; gire o segredo quando quiser (rotate_webhook_secret). |
| Escopo | Todo recurso é escopado por chave: id de outro cliente responde 404, nunca 403 (não vazamos existência). |
| Dados sensíveis | CPF/CNPJ e chaves PIX sempre mascarados nas respostas e webhooks; identificadores internos de provedores nunca são expostos. |
| Abuso | Rate limit por chave (token bucket), criação de chave limitada por IP, idempotência contra double-spend de retry — e, principalmente, limites que sobem com histórico (abaixo). |
| Chave nova, poder pequeno | A credencial é self-service, mas nasce contida: payout desligado (libera só com R$ 1.000 liquidados), tier inicial com teto diário baixo que sobe com volume, e um pagador novo (CPF/CNPJ) preso à escada R$ 60 → R$ 200/24h → R$ 6.000/dia. É o KYC-por-movimentação: quem quer mover mais precisa de histórico, não de formulário. Nenhuma chave anônima começa com poder de uma conta madura. |
| Do seu lado | Chave só em variável de ambiente no servidor; nunca em front-end, app, repositório ou log. Valide webhooks antes de processar; use external_id em tudo. |
Conciliação diária
Como bater o seu dia com o nosso, sem planilha manual:
- Amarre tudo com
external_id(o id do SEU sistema) em cobranças, cash-outs e payouts — é a chave primária da conciliação. - Puxe a janela do dia:
GET /cashin/charges?start=2026-07-21&end=2026-07-22,GET /cash-outs?limit=200eGET /payouts?limit=200. - Confira pelos campos fonte-da-verdade: no cash-in, o valor que vale é
depix_received_cents(o que realmente entrou) esettlement_tx_hash(prova on-chain); no cash-out,state = COMPLETED; no payout,status = sent. - Webhooks são gatilho, a API é extrato: processe eventos de forma idempotente e reconcilie pela listagem — nunca dependa só do webhook pra fechar caixa.
Boas práticas (o checklist de produção)
- ✅ Chave só no servidor; front-end nunca vê
X-API-Key. - ✅ Credite pelo webhook assinado (
cashin.settled/cashout.completed), com verificação de HMAC. - ✅ Use
external_idem toda operação (idempotência grátis). - ✅ Valores: centavos (int) no cash-in, string decimal no cash-out — nunca float.
- ✅ Trate
429/5xxcom retry + backoff exponencial (1s, 2s, 4s…). - ✅ Vincule o Telegram — é o seu monitor em tempo real de graça.
- ✅ Confie no
depix_received_cents, não no valor cotado.
Exemplo completo — checkout PIX → USDT
Um helper mínimo e o fluxo inteiro (criar cobrança → acompanhar → creditar via webhook):
// lunium.js — helper de 15 linhas, zero dependências (Node 18+)
const BASE = 'https://api.luniumpay.com';
export async function lunium(method, path, body) {
const r = await fetch(BASE + path, {
method,
headers: {
'X-API-Key': process.env.LUNIUM_KEY,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const data = await r.json();
if (!r.ok) throw new Error(data.detail || `HTTP ${r.status}`);
return data;
}
// 1) cliente clicou em "pagar com PIX"
const charge = await lunium('POST', '/cashin/charge', {
amount_cents: pedido.totalCents,
payer_tax_number: cliente.cpf,
payer_name: cliente.nome,
external_id: pedido.id, // idempotência
});
render(charge.qr_copypaste); // 2) mostra o copia-e-cola
// 3) crédito REAL chega pelo webhook assinado (veja a seção Webhooks):
// event === 'cashin.settled' → liberar o pedido data.external_idimport os, requests
BASE = "https://api.luniumpay.com"
def lunium(method, path, body=None):
r = requests.request(method, BASE + path, json=body,
headers={"X-API-Key": os.environ["LUNIUM_KEY"]})
data = r.json()
if not r.ok:
raise RuntimeError(data.get("detail", f"HTTP {r.status_code}"))
return data
# 1) cria a cobrança
charge = lunium("POST", "/cashin/charge", {
"amount_cents": pedido.total_cents,
"payer_tax_number": cliente.cpf,
"payer_name": cliente.nome,
"external_id": pedido.id,
})
mostrar(charge["qr_copypaste"]) # 2) exibe o PIX
# 3) credite no webhook 'cashin.settled' (seção Webhooks)Idempotência & recuperação por external_id
Um número que resolve os dois piores momentos de uma integração de pagamento: o
retry que não pode cobrar/pagar duas vezes, e o 500/timeout em que você não sabe se a
operação existiu.
Mande external_id em toda operação de escrita — cash-in, cash-out e payout.
É a sua chave de conciliação, e o contrato é o mesmo nos três:
| Regra | Como funciona |
|---|---|
| Escopo | Único por chave e por produto. O mesmo
external_id pode existir uma vez em cash-in, uma em cash-out e uma em payout — nunca
duas no mesmo produto da mesma chave. |
| Tamanho / formato | Texto de até 64 caracteres, sensível a
maiúsculas (Pedido-1 ≠ pedido-1). Acima de 64 é truncado. |
| Repetir igual | Mesmo external_id + mesmos parâmetros devolve a
operação original com 200 — não cria uma segunda. Retry seguro. |
| Repetir diferente | Mesmo external_id + parâmetros divergentes
devolve 409 external_id_divergente. A operação devolvida seria a ORIGINAL, não a
sua: gere um id novo. |
| Retenção | Permanente. Um external_id usado não volta a ficar
livre — é o que garante a idempotência valer meses depois. |
Recuperação: descubra a verdade após um erro
Levou 500, timeout ou perdeu a resposta? Não repita às cegas — pergunte. A
mesma chave de idempotência é a chave de leitura:
GET /cashin/charges?external_id=pedido-8812
GET /cash-outs?external_id=saque-2207
GET /payouts?external_id=comissao-042
Cada um responde 200 com a lista do produto: um elemento se a operação
existe (com o estado atual), lista vazia se não existe — e aí é seguro criar de novo com o
mesmo external_id. O índice único garante 0 ou 1 resultado. É o antídoto para o caso do
payout em que "o PIX pode ter saído mas a resposta se perdeu": você consulta e sabe.
external_id antes de
chamar; se a chamada falhar de forma ambígua (timeout, 5xx), consulte por ele antes de
qualquer retry. Se achou, use o que voltou; se não achou, repita a criação com o mesmo
external_id — a idempotência cobre o resto.Antes da primeira cobrança real
Sete conferências. Cada uma existe porque já custou dinheiro ou uma madrugada de alguém — não é formalidade.
- Teste o webhook com
POST /webhooks/teste confirme 2xx. É a diferença entre descobrir que o handler está errado agora ou quando o primeiro PIX não chegar. - Verifique a assinatura (
X-Lunium-Signature) e recuse o que tiver mais de ~5 minutos. Nunca processe evento sem verificar. - Deduplique por
event_id. Retentativa nossa é normal; crédito em dobro não. - Mande
external_idem toda ordem. É a chave de conciliação e o que torna a chamada idempotente — repetir devolve a mesma ordem em vez de criar outra. - Ramifique por
erroeacao, nunca pelo texto.detailé para humano e muda sem aviso. - Leia os limites de
GET /keys/me(per_operationepayer_limits) em vez de fixar valores no seu código — eles sobem sozinhos com o seu volume. - Entre no grupo de parceiros. É onde mudança de contrato é anunciada antes de ir ao ar, e onde você fala com a gente quando não pode esperar.
Sandbox não cobra nada e não move dinheiro: POST /keys/sandbox devolve uma
chave lun_test_… na hora, e os 7 itens acima podem ser exercitados inteiros lá.
Versionamento & changelog
Política de compatibilidade: mudanças aditivas (novos campos em respostas, novos
eventos, novos códigos de erro, novos endpoints) acontecem sem aviso — escreva seu parser
para ignorar campos desconhecidos e trate evento ou código desconhecido pelo que o
acao mandar. Mudanças que quebram contrato (remover ou renomear campo, mudar
semântica) são anunciadas com antecedência.
Onde o aviso sai, em ordem de alcance: no
grupo de parceiros no Telegram, no bot da sua chave e
nesta tabela. Se você cadastrou e-mail em POST /keys, também por e-mail — mas a maioria
das chaves não tem e-mail, então não conte só com ele: entre no grupo.
A OpenAPI é o contrato. info.version em
openapi.json sobe em MINOR a cada adição
compatível e em MAJOR se algo quebrar. Um teste automático confere, a cada 6 horas, se a produção
ainda cumpre o que aquele arquivo promete — quando diverge, nós somos avisados antes de você.
| Data | Mudança |
|---|---|
2026-08-26 | Titular da chave PIX antes de pagar: GET /pix/keys/lookup?key= consulta o DICT e devolve nome, CPF/CNPJ mascarado e instituição — é o que permite mostrar "você vai pagar Fulano · Nubank" antes do aceite. Tipo deduzido do formato; cache de 24h; 5 consultas/min na conta inteira (429 limite_consultas com Retry-After); chave inexistente → 404 chave_nao_encontrada. Sandbox devolve um titular de teste na mesma forma. |
2026-08-26 | Cotação reversa no cash-out: POST /cash-outs aceita brl_amount (o valor do PIX em reais) no lugar de amount — exatamente um dos dois. A resposta traz a cripto calculada em amount. Aditivo: quem manda amount não muda nada. O sandbox espelha (na reversa, o gatilho determinístico lê os centavos do brl_amount). Novo código de erro brl_amount_invalido. |
2026-08-08 | Sunset anunciado AÇÃO ATÉ 09/11/2026 — em 9 de novembro de 2026 deixam de existir: (1) a assinatura de webhook v1 (X-Webhook-Signature) — verifique a v2 (X-Lunium-Signature, HMAC de "timestamp.corpo", recuse > 5 min); e (2) o campo code nos corpos de erro — ramifique por erro. Integrações existentes seguem intactas até a data; chaves criadas a partir de 08/08/2026 já recebem somente a v2. |
2026-08-08 | Auditoria de hardening — doc × produção, endpoint por endpoint. Mudanças de comportamento: valor mínimo do cash-in agora é por chave MUDA COMPORTAMENTO (abaixo dele a taxa fixa consumiria tudo e a entrega seria zero — recusa valor_abaixo_do_minimo; leia limits.per_operation.cashin.min_brl_cents em GET /keys/me); redes que exigem memo/tag saíram do /catalog e são recusadas no POST /cash-outs MUDA COMPORTAMENTO (sem o memo o depósito se perdia); exemplo de taxa corrigido — não recalcule a cotação localmente, os campos da API são a fonte de verdade. |
2026-08-08 | Recuperação por external_id: GET /cashin/charges, GET /cash-outs e GET /payouts aceitam ?external_id= e devolvem a operação exata (0 ou 1) — o antídoto para o 500/timeout ambíguo. O 409 de divergência é external_id_divergente nos três produtos, e o replay idêntico devolve 200 também no sandbox. Seção nova: Idempotência & recuperação. |
2026-08-08 | Operabilidade: toda resposta autenticada traz X-RateLimit-Limit/-Remaining/-Reset (e o 429, Retry-After); GET /keys/me ganhou o bloco payout (elegibilidade legível por máquina); PATCH /keys/me aceita rotate_monitor_token; assinatura de webhook v1 marcada como deprecated — verifique a v2 (X-Lunium-Signature). |
2026-08-07 | Webhooks: identidade e anti-replay. O corpo passa a trazer
event_id (evt_…, estável entre retentativas — deduplique por ele) e vão
novos headers: X-Lunium-Event, -Event-Id, -Delivery-Id,
-Attempt, -Timestamp. Nova assinatura X-Lunium-Signature
(t=…,v1=…, HMAC de "t.corpo") que permite recusar entrega antiga; a
X-Webhook-Signature antiga continua igual e válida. Novos:
POST /webhooks/test, GET /webhooks/deliveries e
POST /webhooks/deliveries/{event_id}/retry — evento que esgota as tentativas deixa de se
perder. webhook_url agora é validada também na criação da chave (https, host público). |
2026-08-07 | Toda resposta é rastreável: header X-Request-ID
em tudo e campo request_id em todo erro. Se você mandar X-Request-ID
([A-Za-z0-9._:-]{8,64}), ele é preservado de ponta a ponta. Todo erro passa a trazer
erro e acao — antes 59 dos 74 pontos respondiam só detail. |
2026-08-07 | Sandbox igual à produção: par asset/network
inexistente passa a ser recusado no sandbox com o mesmo 400 da produção — antes ele cotava
rede inventada e o código só quebrava no primeiro dia real. Erros de cash-out passam a trazer
erro/acao e mensagem acionável em vez do repasse cru do serviço interno. |
2026-08-07 | Limites deixam de ser adivinhação: GET /keys/me
passa a publicar per_operation (mínimo e máximo de cash-in, payout e cash-out) e
payer_limits (a escada por CPF/CNPJ). A cota diária passou a cobrar exatamente o escopo que
daily_limit_applies_to declara. Leia daqui em vez de fixar número no cliente. |
2026-08-07 | POST /payouts com external_id ficou
idempotente de verdade: a ordem é reservada antes de o PIX sair, então duas chamadas simultâneas
com o mesmo external_id não pagam duas vezes. Sem resposta do provedor devolve
provedor_sem_resposta com acao: "parar" — consulte, não repita. |
2026-07-31 | Cash-out multi-rede documentado: tabela de redes e prazos, os dois caminhos de liquidação e os mínimos por moeda (USDT 10 · USDC 20). Solana temporariamente indisponível para cash-out — recusada com 400 e fora do /catalog. |
2026-07-30 | Limite por pagador (CPF/CNPJ) MUDA COMPORTAMENTO: escada R$ 60 na estreia → R$ 200 nas primeiras 24 h → R$ 6.000/dia por documento, separada do teto da chave. Estouro devolve 403. |
2026-07-26 | Rail de conversão: cash-out de USDT e USDC nas principais redes (Ethereum, BNB Chain, Arbitrum, Optimism, Base, Avalanche, TON, Aptos, NEAR e outras) pelo mesmo POST /cash-outs — prazo pelas confirmações de cada rede. |
2026-07-21 | Monitor ao vivo por cliente: monitor_url no POST /keys e GET /keys/me (painel web em tempo real, só-leitura, escopado à chave) + GET /cashin/events (JSON) + comando /monitor no agente. Grupo do time virou a recomendação padrão do onboarding. |
2026-07-21 | USDC no cash-in: campo asset ("usdt" padrão | "usdc" nativo da Circle, Polygon) em /cashin/preview e /cashin/charge; respostas de cash-in agora incluem asset. Bot: usdc opcional em /cobrar /qrdelay /cotar /calcular. |
2026-07-21 | Bot vira agente completo: grupos por projeto (vínculo com código lk…), IA com dados reais da conta, /sacar /pagar /cotar /calcular /qrdelay /ping e aliases do bot DePix; dinheiro só com botão de confirmação. |
2026-07-21 | Payouts (PIX BRL direto, sob liberação) + eventos payout.*; GET /ping; filtros start/end em /cashin/charges; comandos /cobrar, /qr e /webhook no bot; assistente técnico no bot. |
2026-07-20 | Lançamento da API unificada: chave self-service (POST /keys), cash-in e cash-out na mesma chave, tiers por movimentação, webhooks assinados de ponta a ponta, bot do Telegram com avisos em tempo real. |
Suporte & contato
Fala direto com
uma pessoa do time, em horário comercial. É o caminho quando o assunto é contrato, limite sob medida ou
um problema em produção que não pode esperar.
Falar com a equipe
O mesmo bot que te avisa
cada transação também tira dúvida técnica a qualquer hora. Link no seu POST /keys /
GET /keys/me.
status mede cash-in, cash-out, entrega de webhook e se a produção ainda cumpre a OpenAPI — a cada 5 minutos, com chamadas de verdade. Confira antes de abrir chamado.
contato@luniumpay.com — Enterprise, parcerias e o que precisar ficar registrado por escrito.
⬡ Lunium — o hub PIX ↔ stablecoin. Esta documentação descreve o comportamento vigente da API em produção; mudanças que quebram contrato são anunciadas com antecedência no grupo de parceiros, no bot da sua chave e na changelog.
AMBIENTE: PRODUÇÃO · MONITORAMENTO 24/7 · CONTATO@LUNIUMPAY.COM