Base: https://SEU-DOMINIO.com/api
- Todas as rotas são POST.
- Envie Content-Type: application/json.
- Autenticação: Basic apenas no token; Bearer JWT nas demais rotas.
Pix + Subcarteiras + Cripto + OTC/P2P (JWT + IP allowlist + Webhook Global)
Base: https://SEU-DOMINIO.com/api
403.POST /v2/oauth/token — header Authorization: Basic base64(client_id:client_secret)
curl -sS -X POST "https://SEU-DOMINIO.com/api/v2/oauth/token" \ -H "Authorization: Basic $(printf '%s' 'CLIENT_ID:CLIENT_SECRET' | base64)"
Resposta:
{ "access_token": "...jwt...", "expires_in": 1800 }
Em todas as rotas /v2/* (exceto /v2/oauth/token):
Authorization: Bearer <access_token>
Todos os exemplos seguem o mesmo padrão: token com Basic em /v2/oauth/token, chamadas com Bearer,
status com wait=1 e webhook global (quando configurado no painel).
client_id e client_secret você pega no seu painel/plataforma. E lembre de cadastrar o IP do seu servidor na allowlist do usuário integrador.
require 'net/http'
require 'json'
require 'base64'
BASE_URL = 'https://SEU-DOMINIO.com/api'
CLIENT_ID = ENV.fetch('LUNARPAY_CLIENT_ID')
CLIENT_SECRET = ENV.fetch('LUNARPAY_CLIENT_SECRET')
def post_json(url, headers, body)
uri = URI(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = (uri.scheme == 'https')
req = Net::HTTP::Post.new(uri)
headers.each { |k, v| req[k] = v }
req.body = body.nil? ? '' : JSON.generate(body)
res = http.request(req)
[res.code.to_i, res.body]
end
basic = Base64.strict_encode64("#{CLIENT_ID}:#{CLIENT_SECRET}")
st, body = post_json("#{BASE_URL}/v2/oauth/token", { 'Authorization' => "Basic #{basic}", 'Content-Type' => 'application/json' }, nil)
token = JSON.parse(body).fetch('access_token')
payload = { amount: 10.50, payerName: 'João da Silva', payerDocument: '123.456.789-09', payerQuestion: 'Pedido #123', external_id: 'pedido-123', expires_in_minutes: 30 }
st, body = post_json("#{BASE_URL}/v2/pix/qrcode", { 'Authorization' => "Bearer #{token}", 'Content-Type' => 'application/json' }, payload)
puts body
# Cripto: obter subcarteira
st, body = post_json("#{BASE_URL}/v2/crypto/wallets", { 'Authorization' => "Bearer #{token}", 'Content-Type' => 'application/json' }, { coin: 'USDT' })
puts body
# P2P: cotação
st, body = post_json("#{BASE_URL}/v2/p2p/quote", { 'Authorization' => "Bearer #{token}", 'Content-Type' => 'application/json' }, { from_coin: 'BRL', to_coin: 'USDT', amount: '100' })
puts body
Script executável pronto: dev/tef.php
<?php
$baseUrl = 'https://SEU-DOMINIO.com/api';
$clientId = getenv('LUNARPAY_CLIENT_ID');
$clientSecret = getenv('LUNARPAY_CLIENT_SECRET');
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $baseUrl . '/v2/oauth/token',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Basic ' . base64_encode($clientId . ':' . $clientSecret),
'Content-Type: application/json',
],
]);
$tokenResp = curl_exec($ch);
curl_close($ch);
$token = (json_decode($tokenResp, true)['access_token'] ?? null);
$payload = [
'amount' => 10.50,
'payerName' => 'João da Silva',
'payerDocument' => '123.456.789-09',
'payerQuestion' => 'Pedido #123',
'external_id' => 'pedido-123',
'expires_in_minutes' => 30,
];
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $baseUrl . '/v2/pix/qrcode',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
],
]);
echo curl_exec($ch);
curl_close($ch);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $baseUrl . '/v2/crypto/wallets',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['coin' => 'USDT']),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
],
]);
echo curl_exec($ch);
curl_close($ch);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $baseUrl . '/v2/p2p/quote',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['from_coin' => 'BRL', 'to_coin' => 'USDT', 'amount' => '100']),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
],
]);
echo curl_exec($ch);
curl_close($ch);
?>
import base64
import json
import os
import urllib.request
BASE_URL = 'https://SEU-DOMINIO.com/api'
CLIENT_ID = os.environ['LUNARPAY_CLIENT_ID']
CLIENT_SECRET = os.environ['LUNARPAY_CLIENT_SECRET']
def post_json(path, headers=None, data=None):
url = BASE_URL + path
h = {'Content-Type': 'application/json'}
if headers:
h.update(headers)
payload = b'' if data is None else json.dumps(data).encode('utf-8')
req = urllib.request.Request(url, data=payload, headers=h, method='POST')
with urllib.request.urlopen(req, timeout=40) as resp:
return resp.status, resp.read().decode('utf-8')
basic = base64.b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()
st, body = post_json('/v2/oauth/token', headers={'Authorization': f'Basic {basic}'}, data=None)
token = json.loads(body)['access_token']
payload = {"amount": 10.50, "payerName": "João da Silva", "payerDocument": "123.456.789-09", "payerQuestion": "Pedido #123", "external_id": "pedido-123", "expires_in_minutes": 30}
st, body = post_json('/v2/pix/qrcode', headers={'Authorization': f'Bearer {token}'}, data=payload)
print(body)
# Cripto: obter subcarteira
st, body = post_json('/v2/crypto/wallets', headers={'Authorization': f'Bearer {token}'}, data={"coin": "USDT"})
print(body)
# P2P: cotação
st, body = post_json('/v2/p2p/quote', headers={'Authorization': f'Bearer {token}'}, data={"from_coin": "BRL", "to_coin": "USDT", "amount": "100"})
print(body)
const baseUrl = 'https://SEU-DOMINIO.com/api';
const clientId = process.env.LUNARPAY_CLIENT_ID;
const clientSecret = process.env.LUNARPAY_CLIENT_SECRET;
async function postJson(path, headers = {}, body = null) {
const res = await fetch(baseUrl + path, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...headers },
body: body === null ? '' : JSON.stringify(body),
});
return { status: res.status, text: await res.text() };
}
(async () => {
const basic = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
const tokenResp = await postJson('/v2/oauth/token', { Authorization: `Basic ${basic}` }, null);
const token = JSON.parse(tokenResp.text).access_token;
const payload = { amount: 10.5, payerName: 'João da Silva', payerDocument: '123.456.789-09', payerQuestion: 'Pedido #123', external_id: 'pedido-123', expires_in_minutes: 30 };
const pixResp = await postJson('/v2/pix/qrcode', { Authorization: `Bearer ${token}` }, payload);
console.log(pixResp.text);
const walletsResp = await postJson('/v2/crypto/wallets', { Authorization: `Bearer ${token}` }, { coin: 'USDT' });
console.log(walletsResp.text);
const quoteResp = await postJson('/v2/p2p/quote', { Authorization: `Bearer ${token}` }, { from_coin: 'BRL', to_coin: 'USDT', amount: '100' });
console.log(quoteResp.text);
})();
Se existirem entradas na tabela ip_whitelist para o seu user_id, a API bloqueia chamadas (token e Bearer)
fora desses IPs. Em Hostinger direto (sem proxy), o IP considerado é REMOTE_ADDR.
Você pode criar o depósito via checkout/pix (checkout) ou pix/qrcode (direto).
Ambos criam uma transação DEPOSIT com status=PENDING.
O brcode é opcional (configurado no banco em config.pix_receiver_*, sem PIX_KEY na env).
Se não estiver configurado, acompanhe confirmação por status/webhook.
POST /v2/pix/qrcode
Body:
{
"amount": 10.50,
"payerName": "João da Silva",
"payerDocument": "123.456.789-09",
"payerQuestion": "Pedido #123",
"external_id": "pedido-123",
"expires_in_minutes": 30
}
Resposta:
{
"status": "PENDING",
"transactionId": "...",
"external_id": "pedido-123",
"message": "Depósito criado. Faça o pagamento via Pix e aguarde confirmação.",
"amount": "10.50",
"tax": "0.50",
"net_amount": "10.00",
"expires_at": "2026-01-11 12:34:56"
}
external_id deve ser único.POST /v2/pix/payment
creditParty.key é a chave PIX de destino (para onde o saque será enviado).creditParty.keyType (opcional) para indicar o tipo da chave (EMAIL, TELEFONE, CPF, CNPJ, EVP).
Body:
{
"amount": 25.00,
"description": "Saque do usuário",
"debtorName": "João da Silva",
"debtorDocument": "123.456.789-09",
"creditParty": { "key": "CHAVE_PIX_DESTINO", "keyType": "EVP" }
}
Resposta:
{
"status": "PENDING",
"transactionId": "...",
"external_id": "...",
"amount": "25.00",
"tax": "0.50",
"total_debit": "25.50"
}
POST /v2/transactions/status
Body (use um deles):
{ "transactionId": "...", "wait": 1, "timeout_ms": 30000, "interval_ms": 1000 }
{ "external_id": "pedido-123", "wait": 1 }
Resposta:
{
"transactionId": "...",
"external_id": "pedido-123",
"type": "DEPOSIT",
"status": "PENDING|PAID|CANCELLED|EXPIRED|REVERSED",
"amount": "10.50",
"tax": "0.50",
"net_amount": "10.00",
"expires_at": "...",
"confirmed_date": null,
"created_at": "..."
}
wait=1 segura a requisição até sair de PENDING (ou até timeout_ms).
Webhook é GLOBAL (por usuário) e aceita apenas HTTPS. Quando a API detectar mudança de status durante a reconciliação (polling), ela faz um POST JSON best-effort para a URL global configurada.
Payload (exemplo):
{
"transactionType": "PIX",
"event": "STATUS_CHANGED",
"transactionId": "...",
"external_id": "pedido-123",
"type": "DEPOSIT",
"status": "PAID",
"amount": 10.5,
"tax": 0.5,
"net_amount": 10.0,
"confirmed_date": "2026-01-11 12:35:10",
"created_at": "2026-01-11 12:05:00",
"end2end": "..."
}
Pré-requisito: tabela webhook_subscriptions (já no dump api/testelunarpay07299.sql).
POST /v2/webhooks/get
{ "urls": ["https://seu-sistema.com/webhook/lunarpay"] }
/v2/transactions/status detecta transição./v2/crypto/deposits/status detecta confirmação./v2/crypto/transactions/status detecta mudança./v2/p2p/status detecta mudança derivada.Importante: a API não envia webhooks em background; dispara quando você consulta status.
POST /v2/crypto/coins
{ "coins": ["BTC","ETH","USDT", "..."] }
POST /v2/crypto/wallets
Body (todas):
{}
Body (uma moeda):
{ "coin": "USDT" }
Resposta:
{
"wallets": [
{ "coin": "USDT", "network": "TRC20", "address": "..." }
]
}
network é fixa por moeda (conforme configuração interna).POST /v2/crypto/deposits/status
{ "coin": "USDT", "address": "...", "wait": 1, "timeout_ms": 30000, "interval_ms": 1000 }
Resposta:
{
"coin": "USDT",
"status": "PENDING|CONFIRMED|...",
"confirmed": true,
"provider_ref": "...",
"amount": "12.34",
"network": "trc20",
"address": "...",
"hash": "..."
}
POST /v2/crypto/withdraw
{
"coin": "USDT",
"address": "...",
"amount": "10.0",
"network": "TRC20",
"pin": "1234"
}
Resposta:
{
"status": "PENDING",
"coin": "USDT",
"network": "trc20",
"amount": "10.00000000",
"platform_fee": "0.10000000",
"total_debit": "10.10000000",
"provider_ref": "..."
}
POST /v2/crypto/transactions/status
{ "provider_ref": "...", "wait": 1 }
{ "id": "...", "wait": 1 }
Resposta:
{
"id": "...",
"direction": "OUT",
"coin": "USDT",
"network": "trc20",
"amount": "10.00000000",
"platform_fee": "0.10000000",
"provider": "LUNARPAY_ADQ",
"provider_ref": "...",
"status": "PENDING|sent|processing|canceled",
"provider_status": "sent|processing|canceled",
"tx_hash": null,
"confirmed_at": null,
"reversed_at": null
}
POST /v2/p2p/quote
{ "from_coin": "BRL", "to_coin": "USDT", "amount": "100" }
Resposta:
{
"from_coin": "BRL",
"to_coin": "USDT",
"amount_in": "100.00000000",
"platform_fee_in": "1.00000000",
"net_to_convert": "99.00000000",
"estimated_out": "19.87654321"
}
POST /v2/p2p/execute
{ "from_coin": "BRL", "to_coin": "USDT", "amount": "100", "pin": "1234" }
Resposta:
{
"status": "COMPLETED",
"group_ref": "...",
"provider_txid_out": "...",
"provider_txid_in": "...",
"from_coin": "BRL",
"to_coin": "USDT",
"amount_in": "100.00000000",
"platform_fee_in": "1.00000000",
"net_converted": "99.00000000"
}
POST /v2/p2p/status
{ "group_ref": "...", "wait": true, "timeout_ms": 30000, "interval_ms": 1000 }
provider_txid (se existir no banco).apply_balance=true tenta aplicar/reverter saldo automaticamente se o provedor mudou o status.Esta API é a intermediária: expõe apenas o necessário para integração (IDs, status, valores e campos operacionais).
Recomendação: ative ip_whitelist e use apenas URLs https para webhook.