Référence publique des endpoints REST de paiement, d’intégration et de portefeuille personnel d’OrionPay. URL de base : https://payapi.orion.moe
Un index lisible par LLM se trouve à /llms.txt et la spécification complète à /llms-full.txt. La spécification OpenAPI 3.1 se trouve à payapi.orion.moe/openapi.json. Les notes de version du produit et de l’API se trouvent à /changelog.
Un serveur MCP Streamable HTTP avec 19 outils est en ligne à payapi.orion.moe/mcp
claude mcp add orionpay --transport http https://payapi.orion.moe/mcp --header "X-API-Key: $ORIONPAY_API_KEY"Générez une clé API dans Dashboard > API & Webhooks > MCP / LLM.
Chaque endpoint public exige une clé API envoyée dans l’en-tête X-API-Key (ou Authorization: Bearer). Il en existe deux types :
productId = null. Contrôle le portefeuille de l’utilisateur (solde, retraits, swap) et crée de nouveaux produits via POST /api/v1/personal/products, qui renvoie la clé API du produit. Ne crée pas de paiements de vente.
Endpoints : /api/v1/personal/*.
Liée à un produit. Nécessaire pour vendre via l’API : elle crée les paiements et déclenche les webhooks de vente. Générée dans le tableau de bord ou renvoyée par POST /api/v1/personal/products lors de la création du produit.
Endpoints : /api/v1/pix/generate, /integration/*.
X-API-Key: opay_abc123def456...// Générer un PIX via l’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) // ID interne de l’achat
console.log(data.pixCode) // PIX copier-coller
console.log(data.qrCode) // QR Code = pixCode
console.log(data.expiresAt) // ISO 8601Pour vendre via l’API
Les paiements de checkout (/api/v1/pix/generate) et les webhooks de vente (purchase.created, payment.success) exigent une Clé API de produit — créez le produit dans le tableau de bord et générez sa clé, ou créez un nouveau produit avec la clé API personnelle via POST /api/v1/personal/products, qui renvoie directement la clé et le webhook. La clé API personnelle (/api/v1/personal/*) gère votre portefeuille et crée de nouveaux produits, mais ne crée aucun paiement. Modifier ou supprimer des produits, envoyer images et contenus, et gérer les clés et webhooks existants restent dans le tableau de bord. Les dépôts sur votre propre compte se font depuis le tableau de bord, pas via l’API.
/api/v1/personal/productsClé API personnelle10 req/hCrée un NOUVEAU produit avec sa clé API de produit et, si fourni, son webhook — en un seul appel ; si une étape échoue, les précédentes sont annulées. Répond 201 (200 en cas de rejeu). Exige la clé API personnelle et un plan Business actif et non expiré (sinon 403 BUSINESS_NOT_ACTIVATED ou BUSINESS_EXPIRED). productType : CURSO_ONLINE, FOTOS, ARQUIVOS, AREA_MEMBROS, TELEGRAM_CHANNEL, CONSULTORIA ou MENTORIA. publish (true par défaut) le publie et crée le lien de checkout ; hideFromMarketplace vaut true par défaut. Sans price (ou avec 0), chaque paiement prend le amount envoyé à pix/generate et paymentLink vaut null ; un price payant inférieur au minimum PIX → 400 MIN_PIX_ORDER_AMOUNT. delivery uniquement pour AREA_MEMBROS ({accessUrl, accessInstructions}) et TELEGRAM_CHANNEL ({channelLink, joinInstructions}). webhook.events par défaut : purchase.created, access.granted, payment.success ; sans webhook.secret, un secret est généré. Champ inconnu → 400 INVALID_FIELD (avec field). Répéter un externalReference renvoie replayed: true avec le même produit et complète les étapes manquantes (apiKey vaut null si la clé du produit a été désactivée ou a expiré dans le tableau de bord — gérez-la là-bas) ; un autre productType ou une autre webhook.url → 409 PRODUCT_REFERENCE_EXISTS. Le plafond de produits créés par 24 h (20 par défaut → 429 API_PRODUCT_DAILY_LIMIT) compte tous les produits du compte, créés par n’importe quel canal, tableau de bord compris ; un rejeu ne compte pas. Comparez les erreurs sur code. apiKey.key et webhook.secret sont des secrets : conservez-les. Le titulaire du compte reçoit un e-mail à chaque création, et révoquer la clé personnelle ne révoque pas les clés de produit créées avec elle. Images et contenus (leçons, fichiers — CURSO_ONLINE, FOTOS et ARQUIVOS restent vides d’ici là), modification et suppression restent dans le tableau de bord. Pour les ventes via pix/generate, OrionPay n’envoie rien à l’acheteur : la livraison vous revient (ex. /acesso/<accessToken>). Outil MCP : personal_create_product.
Corps de la requête
{
"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"
}
}Réponse (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/generateClé API de produitGénère un QR Code PIX lié à un produit. Utilise le prix du produit ou le montant transmis. Le CPF/CNPJ du payeur (champ cpf) est obligatoire depuis le 30/06/2026 — alternative : euid (identifiant du payeur sur DePix).
Corps de la requête
{
"amount": 199.90,
"description": "Venda de produto",
"name": "João da Silva",
"email": "[email protected]",
"cpf": "52998224725",
"phone": "+5511999999999"
}Réponse (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/:idN’importe quelle clé API60 req/minAccepte purchaseId (numérique), transactionId (numérique) ou eulenDepositId (UUID).
Réponse (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-accessClé API de produit60 req/minQuery : email ou accessToken (au moins un obligatoire).
Réponse (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-accessClé API de produitCrée un achat déjà payé pour l’e-mail indiqué. Utile pour les invitations et le support.
Corps de la requête
{
"buyerEmail": "[email protected]",
"buyerName": "Maria Santos",
"price": 199.90,
"expiresAt": "2026-05-24T00:00:00.000Z"
}Réponse (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/buyersClé API de produit60 req/minQuery facultatif : status (PENDING, PAID, EXPIRED).
Réponse (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/:purchaseIdClé API de produitRenvoie les données complètes d’un achat par son ID.
Réponse (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/contentClé API de produit60 req/minQuery : accessToken (obligatoire). Valide le jeton et renvoie les contenus débloqués.
Réponse (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/:purchaseIdClé API de produitInvalide l’accessToken de l’achat indiqué.
Réponse (200)
{
"success": true,
"message": "Acesso revogado com sucesso",
"purchaseId": 12345
}/api/v1/integration/deposit/pixClé API de produit10 req/minCrée un QR Code PIX pour le produit. Le CPF/CNPJ du payeur (champ cpf, chiffres uniquement) est obligatoire depuis le 30/06/2026 — alternative : euid (identifiant du payeur sur DePix). L’identifiant du marchand (merchantId) est automatique (résolu via votre clé API) ; inutile de l’envoyer.
Corps de la requête
{
"cpf": "52998224725",
"amount": 199.90,
"buyerEmail": "[email protected]",
"buyerName": "João da Silva"
}Réponse (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/balanceClé API personnelle60 req/hRenvoie le solde par devise, y compris bloqué et en attente.
Réponse (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/limitsClé API personnelle30 req/hRenvoie les limites quotidiennes/mensuelles et la consommation actuelle.
Réponse (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/transactionsClé API personnelle60 req/hQuery : limit, offset, type (DEPOSIT, WITHDRAWAL, SWAP).
Réponse (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/quoteClé API personnelle120 req/hSimule un retrait avec le même calcul que le débit réel. Envoyez exactement un des deux : ?amount= (brut à débiter) OU ?netAmount= (net souhaité sur le compte de destination). grossAmount − totalFee = netAmount tombe juste.
Réponse (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/pixClé API personnelle10 req/hEnvoyez exactement un des deux : amount (1–100 000, brut à débiter) OU netAmount (net qui doit arriver sur le compte — le débit est calculé automatiquement). fee est le frais TOTAL et amount − fee = netAmount. pixKeyType est détecté automatiquement (EMAIL, CPF, CNPJ, PHONE, RANDOM).
Corps de la requête
{
"netAmount": 75.00,
"pixKey": "12345678000123",
"pixKeyType": "CPF",
"description": "Saque via API",
"taxNumber": "52998224725"
}Réponse (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/cryptoClé API personnelle10 req/hPrend en charge L-BTC, BTC-PEGOUT, DePix, USDt, USDC. memo obligatoire pour TON.
Corps de la requête
{
"amount": 0.01,
"toAddress": "bc1q6y3kz2c8s4n9p1m0l2k3j4h5g6f7e8d9c0b1a",
"assetTicker": "BTC-PEGOUT",
"network": "bitcoin"
}Réponse (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/:idClé API personnelle120 req/hRenvoie l’état actuel, les frais totaux et le montant net. netAmount devient le paiement confirmé par le prestataire dès qu’il est disponible (payoutConfirmed: true). Pour les retraits crypto, toAddress/asset remplacent pixKey.
Réponse (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/quoteClé API personnelle120 req/hDevises prises en charge : DePix, L-BTC, USDt, EURx. Le devis est valable 5 minutes.
Corps de la requête
{
"fromCurrency": "BRL",
"toCurrency": "BTC",
"amount": 1000.00
}Réponse (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/executeClé API personnelle20 req/hDébite fromCurrency et crédite toCurrency au prix en vigueur au moment de l’exécution.
Corps de la requête
{
"fromCurrency": "BRL",
"toCurrency": "BTC",
"amount": 1000.00
}Réponse (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 (sans auth)60 req/minLe seul endpoint sans authentification. Accepte un ID numérique (achat) ou un UUID (eulenDepositId). Prévu pour le polling au checkout.
Réponse (200)
{
"purchaseId": 12345,
"paymentStatus": "PAID",
"paidAt": "2026-04-24T12:00:00.000Z",
"accessToken": "abcd1234efgh5678ijkl9012mnop3456",
"productTitle": "Exemplo Produto",
"price": 199.90
}En cas de dépassement, le backend répond HTTP 429. Les limites sont par clé API + route et se réinitialisent dans la fenêtre indiquée.
Valeurs possibles du champ status sur la ressource Transaction (GET /transactions/:id)
PIX généré, en attente de paiement. Le solde n'a pas encore été déplacé.
Eulen en cours de traitement. Peut être under_review (anti-fraude), delayed (Eulen retient le DePix avant l'approbation — aucun effet financier pour le moment) ou en delay-depix HOLD après l'approved (PIX confirmé et blockedBalance déjà incrémenté ; sous-phase identifiée par metadata.holdSource = "delay-depix" + metadata.approvedAt + availableAt). Webhook de cette sous-phase : deposit.approved_delay.
DePix livré dans la réserve. Solde crédité dans balance (ou dans pendingBalance si isOnHold=true). Webhook émis : deposit.success ou deposit.on_hold.
Échec du PIX (non payé, expiré, refusé par Eulen ou la banque). Aucun solde déplacé.
Transaction annulée manuellement (admin) ou par TTL (PIX non payé dans la fenêtre).
Remboursement : MED (rétrofacturation PIX) détecté pendant la rétention ou remboursement manuel par admin. Solde retiré de blockedBalance ou pendingBalance selon metadata.holdSource.
Transaction en revue manuelle par l'équipe. En attente d'approbation ou de rejet.
Recevez des notifications automatiques lorsque des événements importants se produisent
Chaque webhook porte une signature HMAC-SHA256 dans l’en-tête X-Webhook-Signature : hex en minuscules, sans préfixe, calculée sur le corps brut (raw body) avec le secret du webhook — celui du tableau de bord ou le webhook.secret renvoyé par POST /api/v1/personal/products. Validez toujours avant de traiter.
X-Webhook-Signature: a1b2c3d4e5f6...payment.successPaiement confirmé et crédité.
{
"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.createdAchat créé, en attente de paiement.
{
"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.grantedAccès accordé à l’acheteur après confirmation du paiement.
{
"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_medPaiement remboursé via MED (rétrofacturation) pendant la rétention antifraude.
{
"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.refundedDépôt remboursé par Eulen au payeur (PIX retourné) ; le montant retenu est contre-passé. Un remboursement MED de fonds déjà crédités arrive comme payment.refunded_med. Le champ facultatif refundReasonDetail porte le motif déduit (med_chargeback, cpf_mismatch, cnpj_mismatch, spam_like, user_blocked, payer_refused, indeterminate_payer). Sauf pour indeterminate_payer, la charge utile inclut payerEuid — identifiant pseudonyme et stable du compte payeur, pour corréler les récidivistes sans révéler l’identité. Les motifs mismatch portent aussi payerName et payerTaxNumber, le CPF étant toujours masqué.
{
"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_pendingAvis PRÉALABLE : Eulen a annoncé qu’elle va restituer un PIX déjà reçu (risque/conformité). Ce n’est pas l’événement terminal — payment.refunded suit, en moyenne ~2 h et jusqu’à 3 jours lorsque le prestataire retient le montant au préalable (indiqué dans expectedRefundAt). Considérez-le comme irréversible. Le champ facultatif rejectionReasons porte le code brut du prestataire (PAYER_MISMATCH, BLOCKED_USER) ; le vocabulaire est le sien et peut s’étoffer — traitez un code inconnu comme un refus de risque générique. Si l’acheteur avait déjà accès, OrionPay le révoque et envoie accessRevoked: true ; annulez la livraison de votre côté également.
{
"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 (rétrofacturation) détecté sur un dépôt Eulen.
{
"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.completedRetrait transmis à la blockchain ou à la banque.
{
"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.settledRetrait entièrement confirmé sur le réseau de destination (Peg-Out BTC inclus).
{
"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.failedÉchec du retrait (solde insuffisant, adresse invalide, etc.). Les montants sont déjà revenus au solde.
{
"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.completedSwap atomique exécuté avec succès.
{
"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.failedLe swap a échoué ou a été remboursé.
{
"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.testÉvénement déclenché manuellement depuis le tableau de bord pour tester l’URL.
{
"event": "webhook.test",
"timestamp": "2026-04-24T14:05:00.000Z",
"data": {
"message": "Webhook de teste",
"testId": "test_abc123"
}
}// Valider la signature du webhook (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 })
})