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 19 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) and creates new products through POST /api/v1/personal/products, which returns the product’s API key. 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. Generated in the dashboard or returned by POST /api/v1/personal/products when the product is created.
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 the product in the dashboard and generate its key, or create a new product with the Personal API Key via POST /api/v1/personal/products, which returns the key and the webhook right away. The Personal API Key (/api/v1/personal/*) runs your wallet and creates new products, but creates no charges. Editing or deleting products, uploading images and content, and managing existing keys and webhooks stay in the dashboard. Deposits into your own account are made through the dashboard, not the API.
/api/v1/personal/productsPersonal API Key10 req/hCreates a NEW product together with its Product API Key and, if sent, its webhook — in a single call; if a step fails, the earlier ones are rolled back. Answers 201 (200 on replay). Requires the Personal API Key and an active, unexpired Business plan (otherwise 403 BUSINESS_NOT_ACTIVATED or BUSINESS_EXPIRED). productType: CURSO_ONLINE, FOTOS, ARQUIVOS, AREA_MEMBROS, TELEGRAM_CHANNEL, CONSULTORIA or MENTORIA. publish (default true) publishes it and creates the checkout link; hideFromMarketplace defaults to true. Without price (or with 0), each charge takes the amount sent to pix/generate and paymentLink is null; a paid price below the PIX minimum → 400 MIN_PIX_ORDER_AMOUNT. delivery only for AREA_MEMBROS ({accessUrl, accessInstructions}) and TELEGRAM_CHANNEL ({channelLink, joinInstructions}). webhook.events defaults to purchase.created, access.granted, payment.success; without webhook.secret one is generated. Unknown field → 400 INVALID_FIELD (with field). Repeating an externalReference returns replayed: true with the same product and completes any missing step (apiKey is null if the product key was disabled or expired in the dashboard — manage it there); a different productType or webhook.url → 409 PRODUCT_REFERENCE_EXISTS. The cap on products created per 24h (default 20 → 429 API_PRODUCT_DAILY_LIMIT) counts every product in the account, created through any channel, dashboard included; replays do not count. Match errors on code. apiKey.key and webhook.secret are secrets: store them. The account owner gets an e-mail on every provisioning, and revoking the Personal key does not revoke the product keys created with it. Images and content (lessons, files — CURSO_ONLINE, FOTOS and ARQUIVOS stay empty until then), editing and deleting stay in the dashboard. For pix/generate sales OrionPay sends the buyer nothing: delivery is yours (e.g. /acesso/<accessToken>). MCP tool: personal_create_product.
Request body
{
"title": "Curso X",
"productType": "CURSO_ONLINE",
"description": "Curso completo de exemplo",
"price": 49.9,
"hideFromMarketplace": true,
"publish": true,
"externalReference": "sku-123",
"webhook": {
"url": "https://example.com/webhooks/orionpay",
"events": ["purchase.created", "access.granted", "payment.success"],
"name": "Default"
}
}Response (201)
{
"success": true,
"replayed": false,
"product": {
"id": 123,
"title": "Curso X",
"description": "Curso completo de exemplo",
"productType": "CURSO_ONLINE",
"price": 49.9,
"status": "PUBLISHED",
"hideFromMarketplace": true,
"externalReference": "sku-123",
"createdAt": "2026-09-15T12:00:00.000Z"
},
"paymentLink": {
"slug": "3f9c2a7b1d4e5f60718293a4b5c6d7e8",
"url": "https://pay.orion.moe/p/3f9c2a7b1d4e5f60718293a4b5c6d7e8"
},
"apiKey": {
"id": 456,
"key": "opay_6b1e0f3a9c2d4e5f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f",
"name": "Curso X",
"createdAt": "2026-09-15T12:00:00.000Z"
},
"webhook": {
"id": 789,
"name": "Default",
"url": "https://example.com/webhooks/orionpay",
"events": ["purchase.created", "access.granted", "payment.success"],
"isActive": true,
"secret": "9d4c2b7e1a0f3e5d6c8b9a7f2e1d0c3b4a5f6e7d8c9b0a1f2e3d4c5b6a7f8e9d"
}
}/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: lowercase hex, no prefix, computed over the raw body with the webhook secret — the one from the dashboard or the webhook.secret returned by POST /api/v1/personal/products. 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')
// X-Webhook-Signature = hex(HMAC-SHA256(secret, raw body)), no prefix
function validateWebhook(rawBody, signature, secret) {
// timingSafeEqual throws on length mismatch: reject a missing/malformed header first
if (typeof signature !== 'string' || !/^[0-9a-f]{64}$/i.test(signature)) return false
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 // Buffer: the exact bytes that were signed
if (!validateWebhook(rawBody, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' })
}
const { event, data } = JSON.parse(rawBody.toString('utf8'))
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 })
})