Payout é a conversão Crypto → BRL. O receiver deposita crypto em um endereço fornecido pela plataforma e recebe BRL via PIX.
Etapas
- Cotação payout.
- Criação da ordem — plataforma gera endereço de depósito.
- Depósito crypto — receiver envia para o endereço.
- Conversão — swap para USDC (ou já USDC) → BRL.
- PIX outbound — plataforma envia BRL para a chave PIX configurada.
- Webhook —
order.completed.
Cotação
curl -X POST https://api.astronpay.co/api/v1/quote/payout \
-H "x-api-key: $API_KEY" -H "x-api-secret: $API_SECRET" \
-H "Content-Type: application/json" \
-d '{ "receiverId": "rcv_...", "amountCrypto": 200.00, "sourceToken": "USDC" }'
O campo sourceToken indica o token que o receiver vai depositar. O padrão é "USDC".
Criação da ordem
curl -X POST https://api.astronpay.co/api/v1/payout \
-H "x-api-key: $API_KEY" -H "x-api-secret: $API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"receiverId": "rcv_...",
"quoteId": "qt_...",
"amountCrypto": 200.00,
"pixKey": "12345678901",
"pixKeyType": "CPF",
"recipientName": "João Silva",
"recipientDocument": "12345678901"
}'
A resposta inclui dois campos críticos para o passo seguinte:
depositAddress — endereço Solana da plataforma para onde o crypto deve ser enviado. É o mesmo endereço para todas as orders de payout, então não basta apenas transferir o valor: você também precisa marcar essa transferência como pertencente a esta order específica usando o paymentReference abaixo.
paymentReference — uma PublicKey Solana não custodial (sem chave privada, sem saldo associado) gerada exclusivamente para esta order. É um identificador no padrão Solana Pay que permite à plataforma correlacionar o depósito on-chain com a sua order.
Valores acima do cotado entram como over-deposit e ficam em conciliação manual. Valores abaixo não disparam a conversão — a ordem expira.
Construindo a transação de depósito
Caminho recomendado: POST /payout/build-transfer-tx
A maneira mais simples — e que evita as armadilhas mais comuns (esquecer o reference, blockhash expirado, instruction malformada) — é deixar a plataforma montar a transação para você:
curl -X POST https://api.astronpay.co/api/v1/payout/build-transfer-tx \
-H "x-api-key: $API_KEY" -H "x-api-secret: $API_SECRET" \
-H "Content-Type: application/json" \
-d '{ "orderId": "ord_01hxy..." }'
Resposta:
{
"transaction": "<base64-tx pronta para assinar>",
"blockhash": "Bv2nMq...",
"lastValidBlockHeight": 312415221,
"expiresAt": "2026-04-27T18:32:00.000Z",
"fromAddress": "DhTfJ1x2...",
"depositAddress": "3JcVH1j1...",
"paymentReference": "EKFB2LCy...",
"amountCrypto": 0.204409,
"sourceToken": "USDC"
}
Em seguida, envie a transaction para POST /merchant/wallet/sign-and-send-transaction (se estiver usando a wallet gerenciada do merchant) ou para POST /receivers/{id}/wallet/sign-and-send-transaction. A plataforma assina, paga o gas, transmite e devolve o hash on-chain.
Caminho avançado: montar a transação por conta própria
Se você precisa de controle total (por exemplo, somar instruções extras na mesma transação ou usar uma wallet self-custodial), pode construir a transação manualmente. Para que o depósito seja detectado, a SPL Transfer precisa incluir o paymentReference como account read-only e não-signer. Sem esse account na lista de keys da instruction, a plataforma não tem como vincular a transferência on-chain à sua order — a order fica em AWAITING_DEPOSIT por 24h e expira.
import {
Connection,
PublicKey,
Transaction,
} from '@solana/web3.js';
import {
createTransferInstruction,
getAssociatedTokenAddressSync,
} from '@solana/spl-token';
const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
const fromWallet = new PublicKey('<wallet de origem>');
const depositAddr = new PublicKey(order.depositAddress);
const reference = new PublicKey(order.paymentReference);
const amountSmallestUnit = BigInt(Math.round(order.amountCrypto * 1e6)); // USDC tem 6 decimais
const fromAta = getAssociatedTokenAddressSync(USDC_MINT, fromWallet);
const toAta = getAssociatedTokenAddressSync(USDC_MINT, depositAddr);
const transferIx = createTransferInstruction(fromAta, toAta, fromWallet, amountSmallestUnit);
// 👇 Passo crítico: anexa o paymentReference como account não-signer
transferIx.keys.push({
pubkey: reference,
isSigner: false,
isWritable: false,
});
const tx = new Transaction().add(transferIx);
// Não seta tx.feePayer aqui se você for usar uma das wallets gerenciadas
// pela plataforma (ver "Gas patrocinado" abaixo).
const connection = new Connection('https://api.mainnet-beta.solana.com');
const { blockhash } = await connection.getLatestBlockhash('finalized');
tx.recentBlockhash = blockhash;
const txBase64 = tx.serialize({ requireAllSignatures: false }).toString('base64');
Em seguida, envie txBase64 para um dos endpoints de assinatura — POST /merchant/wallet/sign-and-send-transaction se a origem for a wallet gerenciada do merchant, ou POST /receivers/{id}/wallet/sign-and-send-transaction se for a wallet gerenciada de um receiver.
Gas patrocinado
Quando você usa as wallets gerenciadas pela plataforma (createPrivyWallet: true na criação do receiver, ou a wallet do merchant), o fee da Solana é patrocinado pela plataforma. Isso significa:
- Você não precisa manter SOL nessas wallets para pagar fees.
- Não defina
transaction.feePayer — a plataforma substitui pela sponsor wallet ao enviar.
- Não chame
connection.simulateTransaction() localmente antes de mandar pro nosso endpoint. O RPC público da Solana não enxerga a sponsorship — ele assume que o feePayer da sua tx vai pagar o fee, e a simulação falha com Blockhash not found ou insufficient lamports for fee mesmo quando a transação iria executar normalmente em produção.
- Pegue o
recentBlockhash imediatamente antes de chamar o endpoint (não reutilize um blockhash de mais de ~30s atrás — eles expiram em ~60s).
Se você gerencia a wallet por fora (passou walletAddress na criação do receiver), o gas é por sua conta — mantenha SOL suficiente na wallet pra cobrir os fees (≈ 0.000005 SOL por SPL Transfer).
Status
Mesmo conjunto do payin (ver Ciclo de vida das ordens). Os principais para payout são:
| Status | Quando |
|---|
PENDING | Ordem criada, aguardando depósito crypto. |
AWAITING_DEPOSIT | Endereço gerado, monitorando blockchain. |
DEPOSIT_RECEIVED | Depósito detectado; conversão em andamento. |
PIX_SENDING | PIX outbound sendo enviado. |
PIX_SENT_UNCONFIRMED | PIX enviado, aguardando confirmação. |
COMPLETED | PIX entregue ao receiver. |
FAILED | Erro; ver failureReason. |
EXPIRED | Receiver não depositou dentro do prazo. |
Chaves PIX suportadas
| Tipo | Exemplo |
|---|
CPF | 12345678901 |
CNPJ | 12345678000190 |
EMAIL | usuario@example.com |
PHONE | +5511999999999 |
RANDOM | chave aleatória (UUID v4) |
QRCODE | payload EMV completo |
Webhooks recebidos
order.deposit_received
order.processing
order.pix_sending
order.completed
order.failed
Payloads completos em Eventos de webhook.
Consultando uma ordem
curl https://api.astronpay.co/api/v1/payout/$ORDER_ID \
-H "x-api-key: $API_KEY" -H "x-api-secret: $API_SECRET"