Public reference for the OrionPay REST endpoints for payments, integration and personal wallet. Base URL: https://payapi.orion.moe
An LLM-friendly index lives at /llms.txt and the full inline spec at /llms-full.txt. The OpenAPI 3.1 spec is at payapi.orion.moe/openapi.json. Product & API release notes live at /changelog.
A Streamable HTTP MCP server with 18 tools is live at payapi.orion.moe/mcp
claude mcp add orionpay --transport http https://payapi.orion.moe/mcp --header "X-API-Key: $ORIONPAY_API_KEY"Generate an API key at Dashboard > API & Webhooks > MCP / LLM.
Every public endpoint requires an API Key sent in the header X-API-Key (or Authorization: Bearer). There are two types:
productId = null. Controls the user’s own wallet (balance, withdrawals, swap). Does not create sales charges.
Endpoints: /api/v1/personal/*.
Bound to a product. Required to sell through the API: it creates charges and fires the sales webhooks.
Endpoints: /api/v1/pix/generate, /integration/*.
X-API-Key: opay_abc123def456...// Generate PIX via API
const response = await fetch('https://payapi.orion.moe/api/v1/pix/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'opay_sua_chave_aqui'
},
body: JSON.stringify({
amount: 199.90,
name: 'João da Silva',
email: '[email protected]'
})
})
const { data } = await response.json()
console.log(data.purchaseId) // internal purchase ID
console.log(data.pixCode) // PIX copy-and-paste
console.log(data.qrCode) // QR Code = pixCode
console.log(data.expiresAt) // ISO 8601To sell through the API
Checkout charges (/api/v1/pix/generate) and sales webhooks (purchase.created, payment.success) require a Product API Key — create a product in the dashboard and generate its key. The Personal API Key only reaches your own wallet (/api/v1/personal/*) and creates no charges. Deposits into your own account are made through the dashboard, not the API.
/api/v1/pix/generateProduct API KeyGenerates a PIX QR Code bound to a product. Uses the product price or the amount you send. The payer’s CPF/CNPJ (field cpf) is required since 30/06/2026 — alternative: euid (the payer’s DePix identifier).
Request body
{
"amount": 199.90,
"description": "Venda de produto",
"name": "João da Silva",
"email": "[email protected]",
"cpf": "52998224725",
"phone": "+5511999999999"
}Response (200)
{
"success": true,
"data": {
"purchaseId": 12345,
"productId": 8,
"productTitle": "Exemplo Produto",
"id": "550e8400-e29b-41d4-a716-446655440000",
"pixCode": "00020126580014br.gov.bcb.pix...",
"qrCode": "00020126580014br.gov.bcb.pix...",
"amount": 199.90,
"description": "Venda de produto: Exemplo Produto",
"expiresAt": "2026-04-24T14:30:00.000Z",
"status": "PENDING",
"coProducers": [
{ "userId": 5, "name": "Co-produtor Name", "percentage": 15.50 }
]
}
}/api/v1/pix/status/:idAny API Key60 req/minAccepts purchaseId (numeric), transactionId (numeric) or eulenDepositId (UUID).
Response (200)
{
"success": true,
"type": "product_purchase",
"id": 12345,
"eulenDepositId": "550e8400-e29b-41d4-a716-446655440000",
"status": "PAID",
"paidAt": "2026-04-24T12:00:00.000Z",
"accessToken": "abcd1234efgh5678ijkl9012mnop3456",
"productId": 8,
"productTitle": "Exemplo Produto",
"amount": 199.90,
"buyerEmail": "[email protected]",
"buyerName": "João Silva",
"createdAt": "2026-04-24T10:00:00.000Z"
}/integration/verify-accessProduct API Key60 req/minQuery: email or accessToken (at least one required).
Response (200)
{
"hasAccess": true,
"purchaseId": 12345,
"accessToken": "abcd1234efgh5678ijkl9012mnop3456",
"buyerEmail": "[email protected]",
"buyerName": "João Silva",
"productTitle": "Exemplo Produto",
"purchaseDate": "2026-04-20T10:00:00.000Z",
"expiresAt": "2026-05-20T10:00:00.000Z"
}/integration/grant-accessProduct API KeyCreates an already-paid purchase for the given email. Useful for comps and support.
Request body
{
"buyerEmail": "[email protected]",
"buyerName": "Maria Santos",
"price": 199.90,
"expiresAt": "2026-05-24T00:00:00.000Z"
}Response (200)
{
"success": true,
"message": "Acesso concedido com sucesso",
"purchaseId": 12347,
"accessToken": "abcd1234efgh5678ijkl9012mnop3456",
"buyerEmail": "[email protected]",
"buyerName": "Maria Santos",
"expiresAt": "2026-05-24T00:00:00.000Z"
}/integration/buyersProduct API Key60 req/minOptional query: status (PENDING, PAID, EXPIRED).
Response (200)
{
"total": 2,
"buyers": [
{
"purchaseId": 12345,
"buyerEmail": "[email protected]",
"buyerName": "João Silva",
"price": 199.90,
"paymentStatus": "PAID",
"accessToken": "abcd1234efgh5678ijkl9012mnop3456",
"purchaseDate": "2026-04-20T10:00:00.000Z",
"paidAt": "2026-04-20T10:05:00.000Z",
"expiresAt": "2026-05-20T10:00:00.000Z",
"paymentLink": "Link de Pagamento Exemplo"
}
]
}/integration/buyers/:purchaseIdProduct API KeyReturns the full data of a purchase by ID.
Response (200)
{
"purchaseId": 12345,
"buyerEmail": "[email protected]",
"buyerName": "João Silva",
"price": 199.90,
"paymentStatus": "PAID",
"accessToken": "abcd1234efgh5678ijkl9012mnop3456",
"purchaseDate": "2026-04-20T10:00:00.000Z",
"paidAt": "2026-04-20T10:05:00.000Z",
"expiresAt": "2026-05-20T10:00:00.000Z",
"product": { "id": 8, "title": "Exemplo Produto", "productType": "COURSE" },
"paymentLink": { "id": 1, "title": "Link de Pagamento Exemplo" }
}/integration/contentProduct API Key60 req/minQuery: accessToken (required). Validates the token and returns the released content.
Response (200)
{
"productId": 8,
"buyerEmail": "[email protected]",
"buyerName": "João Silva",
"contents": [
{
"id": 1,
"type": "video",
"title": "Aula 1 - Introdução",
"url": "https://storage.example.com/video1.mp4",
"fileSize": 524288000,
"mimeType": "video/mp4",
"order": 1,
"metadata": "{\"duration\": 3600}"
}
]
}/integration/revoke-access/:purchaseIdProduct API KeyInvalidates the accessToken of the given purchase.
Response (200)
{
"success": true,
"message": "Acesso revogado com sucesso",
"purchaseId": 12345
}/api/v1/integration/deposit/pixProduct API Key10 req/minCreates a PIX QR Code for the product. The payer’s CPF/CNPJ (field cpf, digits only) is required since 30/06/2026 — alternative: euid (the payer’s DePix identifier). The merchant identifier (merchantId) is automatic (resolved from your API key); you do not need to send it.
Request body
{
"cpf": "52998224725",
"amount": 199.90,
"buyerEmail": "[email protected]",
"buyerName": "João da Silva"
}Response (200)
{
"purchaseId": 12348,
"productId": 8,
"productTitle": "Exemplo Produto",
"id": "550e8400-e29b-41d4-a716-446655440000",
"pixCode": "00020126580014br.gov.bcb.pix...",
"qrCode": "00020126580014br.gov.bcb.pix...",
"amount": 199.90,
"expiresAt": "2026-04-24T14:30:00.000Z",
"status": "created"
}/api/v1/personal/balancePersonal API Key60 req/hReturns the balance per currency, including blocked and pending.
Response (200)
{
"success": true,
"wallets": [
{
"currency": "BRL",
"balance": 5000.00,
"blockedBalance": 500.00,
"pendingBalance": 0.00,
"availableBalance": 4500.00
},
{
"currency": "L-BTC",
"balance": 0.02,
"blockedBalance": 0.00,
"pendingBalance": 0.00,
"availableBalance": 0.02
}
]
}/api/v1/personal/limitsPersonal API Key30 req/hReturns daily/monthly limits and current usage.
Response (200)
{
"success": true,
"limits": {
"dailyWithdrawBRL": 10000.00,
"monthlyWithdrawBRL": 100000.00,
"dailyWithdrawCrypto": 2.00,
"monthlyWithdrawCrypto": 10.00,
"dailyUsed": 2000.00,
"monthlyUsed": 15000.00
}
}/api/v1/personal/transactionsPersonal API Key60 req/hQuery: limit, offset, type (DEPOSIT, WITHDRAWAL, SWAP).
Response (200)
{
"success": true,
"transactions": [
{
"id": 789,
"type": "DEPOSIT",
"status": "COMPLETED",
"amount": 500.00,
"description": "Depósito PIX",
"blockchainTxId": null,
"createdAt": "2026-04-24T10:00:00.000Z"
}
],
"pagination": { "total": 50, "limit": 10, "offset": 0, "hasMore": true }
}/api/v1/personal/withdraw/quotePersonal API Key120 req/hSimulates a withdrawal with the same maths as the real debit. Send exactly one: ?amount= (gross to debit) OR ?netAmount= (net wanted in the destination account). grossAmount − totalFee = netAmount balances exactly.
Response (200)
{
"success": true,
"quote": { "grossAmount": 78.31, "totalFee": 3.31, "netAmount": 75.00, "requestedNetAmount": 75.00 },
"limits": { "minAmount": 4.00, "maxAmount": 50000.00 },
"validation": { "withinLimits": true }
}/api/v1/personal/withdraw/pixPersonal API Key10 req/hSend exactly one: amount (1–100,000, gross to debit) OR netAmount (net that must land in the account — the debit is computed for you). fee is the TOTAL fee and amount − fee = netAmount. pixKeyType is detected automatically (EMAIL, CPF, CNPJ, PHONE, RANDOM).
Request body
{
"netAmount": 75.00,
"pixKey": "12345678000123",
"pixKeyType": "CPF",
"description": "Saque via API",
"taxNumber": "52998224725"
}Response (200)
{
"success": true,
"message": "Saque PIX em processamento",
"queueId": 88,
"transactionId": 791,
"status": "PENDING",
"requiresApproval": false,
"amount": 78.31,
"fee": 3.31,
"total": 78.31,
"netAmount": 75.00,
"requestedNetAmount": 75.00,
"estimatedProcessingTime": "1-2 minutos"
}/api/v1/personal/withdraw/cryptoPersonal API Key10 req/hSupports L-BTC, BTC-PEGOUT, DePix, USDt, USDC. memo is required for TON.
Request body
{
"amount": 0.01,
"toAddress": "bc1q6y3kz2c8s4n9p1m0l2k3j4h5g6f7e8d9c0b1a",
"assetTicker": "BTC-PEGOUT",
"network": "bitcoin"
}Response (200)
{
"success": true,
"transactionId": 792,
"status": "PENDING",
"amount": 0.01,
"assetTicker": "BTC-PEGOUT",
"toAddress": "bc1q6y3kz2c8s4n9p1m0l2k3j4h5g6f7e8d9c0b1a",
"blockchainTxId": null,
"createdAt": "2026-04-24T14:00:00.000Z"
}/api/v1/personal/withdraw/status/:idPersonal API Key120 req/hReturns the current state, the total fee and the net amount. netAmount becomes the payout confirmed by the provider as soon as it is available (payoutConfirmed: true). For crypto withdrawals, toAddress/asset are filled instead of pixKey.
Response (200)
{
"success": true,
"transaction": {
"id": 791,
"status": "COMPLETED",
"amount": 78.31,
"fee": 3.31,
"netAmount": 75.00,
"payoutConfirmed": true,
"requestedNetAmount": 75.00,
"pixKey": "12345678000123",
"description": "P-API: Saque PIX",
"blockchainTxId": null,
"errorCode": null,
"errorMessage": null,
"createdAt": "2026-04-24T14:00:00.000Z",
"updatedAt": "2026-04-24T14:05:00.000Z"
}
}/api/v1/personal/swap/quotePersonal API Key120 req/hSupported currencies: DePix, L-BTC, USDt, EURx. The quote is valid for 5 minutes.
Request body
{
"fromCurrency": "BRL",
"toCurrency": "BTC",
"amount": 1000.00
}Response (200)
{
"success": true,
"fromCurrency": "BRL",
"toCurrency": "BTC",
"fromAmount": 1000.00,
"toAmount": 0.0195,
"rate": 0.0000195,
"fee": 5.00,
"feeCurrency": "BRL",
"expiresAt": "2026-04-24T14:15:00.000Z"
}/api/v1/personal/swap/executePersonal API Key20 req/hDebits fromCurrency and credits toCurrency at the price in force at execution time.
Request body
{
"fromCurrency": "BRL",
"toCurrency": "BTC",
"amount": 1000.00
}Response (200)
{
"success": true,
"transactionId": 793,
"fromCurrency": "BRL",
"toCurrency": "BTC",
"fromAmount": 1000.00,
"toAmount": 0.0195,
"status": "COMPLETED",
"executedAt": "2026-04-24T14:05:00.000Z"
}/payment-links/purchase/:purchaseId/statusPublic (no auth)60 req/minThe only endpoint without auth. Accepts a numeric ID (purchase) or a UUID (eulenDepositId). Meant for checkout polling.
Response (200)
{
"purchaseId": 12345,
"paymentStatus": "PAID",
"paidAt": "2026-04-24T12:00:00.000Z",
"accessToken": "abcd1234efgh5678ijkl9012mnop3456",
"productTitle": "Exemplo Produto",
"price": 199.90
}When the limit is exceeded the backend answers HTTP 429. Limits are per API Key + route and reset within the window shown.
Possible values of the status field on the Transaction resource (GET /transactions/:id)
PIX generated, awaiting payment. Balance not yet moved.
Eulen processing. May be under_review (anti-fraud), delayed (Eulen holding the DePix before approval — no financial effect yet), or in delay-depix HOLD after approved (PIX confirmed and blockedBalance already incremented; sub-phase identified by metadata.holdSource = "delay-depix" + metadata.approvedAt + availableAt). Webhook for this sub-phase: deposit.approved_delay.
DePix delivered to the reserve. Funds credited to balance (or pendingBalance if isOnHold=true). Webhook emitted: deposit.success or deposit.on_hold.
PIX failed (unpaid, expired, refused by Eulen or bank). No balance moved.
Transaction cancelled manually (admin) or by TTL (PIX unpaid within the window).
Refund: MED (PIX chargeback) detected during hold, or manual reversal by admin. Funds removed from blockedBalance or pendingBalance depending on metadata.holdSource.
Transaction under manual review by the team. Awaiting approval or rejection.
Receive automatic notifications when important events happen
Every webhook carries an HMAC-SHA256 signature in the X-Webhook-Signature header, computed over the raw body with the webhook secret. Always validate before processing.
X-Webhook-Signature: a1b2c3d4e5f6...payment.successPayment confirmed and credited.
{
"event": "payment.success",
"productId": 8,
"productTitle": "Exemplo Produto",
"webhookName": "notificar_sistema",
"timestamp": "2026-04-24T12:00:00.000Z",
"data": {
"purchaseId": 12345,
"transactionId": "txn_abc123",
"productId": 8,
"buyerEmail": "[email protected]",
"buyerName": "João Silva",
"accessToken": "abcd1234efgh5678ijkl9012mnop3456",
"price": 199.90,
"netAmount": 179.91,
"platformFee": 19.99,
"timestamp": "2026-04-24T12:00:00.000Z"
}
}purchase.createdPurchase created, awaiting payment.
{
"event": "purchase.created",
"productId": 8,
"timestamp": "2026-04-24T10:00:00.000Z",
"data": {
"purchaseId": 12345,
"transactionId": "txn_abc123",
"amount": 199.90,
"status": "pending",
"createdAt": "2026-04-24T10:00:00.000Z",
"buyerEmail": "[email protected]",
"buyerName": "João Silva"
}
}access.grantedAccess released to the buyer after payment confirmation.
{
"event": "access.granted",
"productId": 8,
"timestamp": "2026-04-24T12:00:05.000Z",
"data": {
"purchaseId": 12345,
"buyerEmail": "[email protected]",
"accessToken": "abcd1234efgh5678ijkl9012mnop3456",
"expiresAt": "2026-05-24T12:00:05.000Z",
"grantedAt": "2026-04-24T12:00:05.000Z"
}
}payment.refunded_medPayment refunded through MED (chargeback) during the anti-fraud hold.
{
"event": "payment.refunded_med",
"productId": 8,
"timestamp": "2026-04-24T15:00:00.000Z",
"data": {
"transactionId": 789,
"amount": 199.90,
"reference": "550e8400-e29b-41d4-a716-446655440000",
"status": "refunded",
"type": "DEPOSIT",
"currency": "BRL",
"reason": "med_during_antifraud_hold",
"medDetectedAt": "2026-04-24T14:50:00.000Z",
"refundedAt": "2026-04-24T15:00:00.000Z"
}
}payment.refundedDeposit refunded by Eulen to the payer (PIX returned); the held amount is reversed. A MED reversal of already-credited funds arrives as payment.refunded_med. The optional refundReasonDetail field carries the inferred reason (med_chargeback, cpf_mismatch, cnpj_mismatch, spam_like, user_blocked, payer_refused, indeterminate_payer). Except for indeterminate_payer, the payload includes payerEuid — a stable pseudonymous id for the paying account, to correlate repeat offenders without exposing identity. The mismatch reasons also carry payerName and payerTaxNumber, with the CPF always masked.
{
"event": "payment.refunded",
"productId": 8,
"timestamp": "2026-04-24T15:00:00.000Z",
"data": {
"transactionId": 789,
"amount": 199.90,
"reference": "550e8400-e29b-41d4-a716-446655440000",
"status": "refunded",
"type": "DEPOSIT",
"currency": "BRL",
"reason": "eulen_refund",
"refundReasonDetail": "cpf_mismatch",
"payerEuid": "EU015562310201680",
"payerName": "João da Silva",
"payerTaxNumber": "***.456.789-**",
"eulenStatus": "refunded",
"refundedAt": "2026-04-24T15:00:00.000Z",
"credited": false
}
}payment.refund_pendingADVANCE notice: Eulen announced it will return a PIX it already received (risk/compliance). This is not the terminal event — payment.refunded follows, on average ~2h and up to 3 days when the provider holds the amount first (reported in expectedRefundAt). Treat it as irreversible. The optional rejectionReasons field carries the provider’s raw code (PAYER_MISMATCH, BLOCKED_USER); the vocabulary is theirs and may grow — treat an unknown code as a generic risk refusal. If the buyer already had access, OrionPay revokes it and sends accessRevoked: true; reverse the delivery on your side too.
{
"event": "payment.refund_pending",
"productId": 8,
"timestamp": "2026-04-24T15:00:00.000Z",
"data": {
"transactionId": 789,
"amount": 199.90,
"reference": "550e8400-e29b-41d4-a716-446655440000",
"status": "refund_pending",
"type": "DEPOSIT",
"currency": "BRL",
"reason": "eulen_will_refund",
"rejectionReasons": ["PAYER_MISMATCH"],
"expectedRefundAt": "2026-04-27T15:00:00.000Z",
"payerEuid": "EU015562310201680",
"eulenStatus": "will_refund",
"credited": false,
"accessRevoked": true,
"purchaseId": 12345,
"productId": 8,
"productTitle": "Exemplo Produto",
"buyerEmail": "[email protected]",
"buyerName": "Cliente Exemplo"
}
}med.createdMED (chargeback) detected on an Eulen deposit.
{
"event": "med.created",
"timestamp": "2026-04-24T14:50:00.000Z",
"data": {
"qrId": "550e8400-e29b-41d4-a716-446655440000",
"bankTxId": "E00000000202604241450",
"amount": 199.90,
"currency": "BRL",
"medDetectedAt": "2026-04-24T14:50:00.000Z",
"depositStatus": "depix_sent",
"payerTaxNumber": "52998224725",
"transactionId": 789,
"purchaseId": 12345,
"productId": 8,
"productTitle": "Exemplo Produto"
}
}withdrawal.completedWithdrawal broadcast to the blockchain or the bank.
{
"event": "withdrawal.completed",
"timestamp": "2026-04-24T14:05:00.000Z",
"data": {
"withdrawalId": 792,
"amount": 0.01,
"fee": 0.00001,
"totalDeducted": 0.01001,
"asset": "L-BTC",
"toAddress": "lq1qqw...",
"txHash": "abc123...",
"network": "liquid",
"isSideShift": false,
"completedAt": "2026-04-24T14:05:00.000Z"
}
}withdrawal.settledWithdrawal fully confirmed on the destination network (includes BTC Peg-Out).
{
"event": "withdrawal.settled",
"timestamp": "2026-04-24T14:30:00.000Z",
"data": {
"withdrawalId": 792,
"amount": 0.01,
"asset": "BTC-PEGOUT",
"toAddress": "bc1q...",
"liquidTxHash": "abc123...",
"settleTxHash": "def456...",
"settleNetwork": "bitcoin",
"completedAt": "2026-04-24T14:30:00.000Z"
}
}withdrawal.failedWithdrawal failed (insufficient balance, invalid address, etc). Funds already returned to the balance.
{
"event": "withdrawal.failed",
"timestamp": "2026-04-24T14:05:00.000Z",
"data": {
"withdrawalId": 792,
"amount": 0.01,
"asset": "L-BTC",
"toAddress": "lq1qqw...",
"network": "liquid",
"error": "Insufficient hotwallet balance",
"failedAt": "2026-04-24T14:05:00.000Z"
}
}swap.completedAtomic swap executed successfully.
{
"event": "swap.completed",
"timestamp": "2026-04-24T14:05:00.000Z",
"data": {
"txid": "abc123...",
"fromCurrency": "L-BTC",
"toCurrency": "DePix",
"fromAmount": 0.01,
"toAmountGross": 3500.00,
"toAmountNet": 3482.50,
"feePercent": 0.5,
"feeAmount": 17.50,
"completedAt": "2026-04-24T14:05:00.000Z"
}
}swap.failedSwap failed or was refunded.
{
"event": "swap.failed",
"timestamp": "2026-04-24T14:05:00.000Z",
"data": {
"transactionId": 793,
"fromCurrency": "L-BTC",
"toCurrency": "DePix",
"fromAmount": 0.01,
"step": "REFUNDED",
"reason": "Manual refund by admin",
"refundedAt": "2026-04-24T14:05:00.000Z"
}
}webhook.testEvent fired manually from the dashboard to test the URL.
{
"event": "webhook.test",
"timestamp": "2026-04-24T14:05:00.000Z",
"data": {
"message": "Webhook de teste",
"testId": "test_abc123"
}
}// Validate webhook signature (Node.js)
const crypto = require('crypto')
function validateWebhook(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex')
)
}
app.post('/webhooks/orionpay', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature']
const rawBody = req.body.toString('utf8')
if (!validateWebhook(rawBody, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' })
}
const { event, data } = JSON.parse(rawBody)
switch (event) {
case 'payment.success':
grantAccess(data.buyerEmail, data.accessToken)
break
case 'payment.refunded_med':
case 'payment.refunded':
case 'payment.refund_pending':
revokeAccess(data.transactionId)
break
case 'withdrawal.settled':
markWithdrawalSettled(data.withdrawalId)
break
}
res.status(200).json({ received: true })
})