Referencia pública de los endpoints REST de pago, integración y billetera personal de OrionPay. URL base: https://payapi.orion.moe
Un índice legible por LLM está en /llms.txt y la especificación completa en /llms-full.txt. La especificación OpenAPI 3.1 está en payapi.orion.moe/openapi.json. Las notas de versión del producto y de la API están en /changelog.
Un servidor MCP Streamable HTTP con 18 herramientas está activo en payapi.orion.moe/mcp
claude mcp add orionpay --transport http https://payapi.orion.moe/mcp --header "X-API-Key: $ORIONPAY_API_KEY"Genera una API Key en Dashboard > API & Webhooks > MCP / LLM.
Todo endpoint público exige una API Key enviada en el header X-API-Key (o Authorization: Bearer). Hay dos tipos:
productId = null. Controla la billetera del propio usuario (saldo, retiros, swap). No genera cobros de venta.
Endpoints: /api/v1/personal/*.
Vinculada a un producto. Necesaria para vender vía API: genera cobros y dispara los webhooks de venta.
Endpoints: /api/v1/pix/generate, /integration/*.
X-API-Key: opay_abc123def456...// Generar PIX vía 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 interno de la compra
console.log(data.pixCode) // PIX copia y pega
console.log(data.qrCode) // QR Code = pixCode
console.log(data.expiresAt) // ISO 8601Para vender vía API
Cobros de checkout (/api/v1/pix/generate) y webhooks de venta (purchase.created, payment.success) exigen una API Key de Producto — crea un producto en el panel y genera su clave. La API Key Personal solo accede a tu billetera (/api/v1/personal/*) y no genera cobros. Los depósitos en tu propia cuenta se hacen por el panel, no por la API.
/api/v1/pix/generateAPI Key de ProductoGenera un código QR PIX vinculado a un producto. Usa el precio del producto o el importe informado. El CPF/CNPJ del pagador (campo cpf) es obligatorio desde el 30/06/2026 — alternativa: euid (identificación del pagador en DePix).
Cuerpo de la petición
{
"amount": 199.90,
"description": "Venda de produto",
"name": "João da Silva",
"email": "[email protected]",
"cpf": "52998224725",
"phone": "+5511999999999"
}Respuesta (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/:idCualquier API Key60 req/minAcepta purchaseId (numérico), transactionId (numérico) o eulenDepositId (UUID).
Respuesta (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-accessAPI Key de Producto60 req/minQuery: email o accessToken (al menos uno obligatorio).
Respuesta (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-accessAPI Key de ProductoCrea una compra ya pagada para el email indicado. Útil para cortesías y soporte.
Cuerpo de la petición
{
"buyerEmail": "[email protected]",
"buyerName": "Maria Santos",
"price": 199.90,
"expiresAt": "2026-05-24T00:00:00.000Z"
}Respuesta (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/buyersAPI Key de Producto60 req/minQuery opcional: status (PENDING, PAID, EXPIRED).
Respuesta (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/:purchaseIdAPI Key de ProductoDevuelve los datos completos de una compra por ID.
Respuesta (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/contentAPI Key de Producto60 req/minQuery: accessToken (obligatorio). Valida el token y devuelve los contenidos liberados.
Respuesta (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/:purchaseIdAPI Key de ProductoInvalida el accessToken de la compra indicada.
Respuesta (200)
{
"success": true,
"message": "Acesso revogado com sucesso",
"purchaseId": 12345
}/api/v1/integration/deposit/pixAPI Key de Producto10 req/minCrea un código QR PIX para el producto. El CPF/CNPJ del pagador (campo cpf, solo dígitos) es obligatorio desde el 30/06/2026 — alternativa: euid (identificación del pagador en DePix). El identificador del comercio (merchantId) es automático (se resuelve por tu API key); no hace falta enviarlo.
Cuerpo de la petición
{
"cpf": "52998224725",
"amount": 199.90,
"buyerEmail": "[email protected]",
"buyerName": "João da Silva"
}Respuesta (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/balanceAPI Key Personal60 req/hDevuelve el saldo por moneda, incluyendo bloqueado y pendiente.
Respuesta (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/limitsAPI Key Personal30 req/hDevuelve límites diarios/mensuales y el consumo actual.
Respuesta (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/transactionsAPI Key Personal60 req/hQuery: limit, offset, type (DEPOSIT, WITHDRAWAL, SWAP).
Respuesta (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/quoteAPI Key Personal120 req/hSimula un retiro con la misma matemática del débito real. Envía exactamente uno: ?amount= (bruto a debitar) O ?netAmount= (neto deseado en la cuenta destino). grossAmount − totalFee = netAmount cuadra exacto.
Respuesta (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/pixAPI Key Personal10 req/hEnvía exactamente uno: amount (1–100.000, bruto a debitar) O netAmount (neto que debe llegar a la cuenta — el débito se calcula solo). fee es la tarifa TOTAL y amount − fee = netAmount. pixKeyType se detecta automáticamente (EMAIL, CPF, CNPJ, PHONE, RANDOM).
Cuerpo de la petición
{
"netAmount": 75.00,
"pixKey": "12345678000123",
"pixKeyType": "CPF",
"description": "Saque via API",
"taxNumber": "52998224725"
}Respuesta (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/cryptoAPI Key Personal10 req/hAdmite L-BTC, BTC-PEGOUT, DePix, USDt, USDC. memo obligatorio para TON.
Cuerpo de la petición
{
"amount": 0.01,
"toAddress": "bc1q6y3kz2c8s4n9p1m0l2k3j4h5g6f7e8d9c0b1a",
"assetTicker": "BTC-PEGOUT",
"network": "bitcoin"
}Respuesta (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/:idAPI Key Personal120 req/hDevuelve el estado actual, la tarifa total y el importe neto. netAmount pasa a ser el payout confirmado por el proveedor en cuanto está disponible (payoutConfirmed: true). Para retiros en cripto se rellenan toAddress/asset en lugar de pixKey.
Respuesta (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/quoteAPI Key Personal120 req/hMonedas admitidas: DePix, L-BTC, USDt, EURx. La cotización vale 5 minutos.
Cuerpo de la petición
{
"fromCurrency": "BRL",
"toCurrency": "BTC",
"amount": 1000.00
}Respuesta (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/executeAPI Key Personal20 req/hDebita fromCurrency y acredita toCurrency al precio vigente en el momento de la ejecución.
Cuerpo de la petición
{
"fromCurrency": "BRL",
"toCurrency": "BTC",
"amount": 1000.00
}Respuesta (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/statusPúblico (sin auth)60 req/minEl único endpoint sin auth. Acepta ID numérico (compra) o UUID (eulenDepositId). Pensado para polling en el checkout.
Respuesta (200)
{
"purchaseId": 12345,
"paymentStatus": "PAID",
"paidAt": "2026-04-24T12:00:00.000Z",
"accessToken": "abcd1234efgh5678ijkl9012mnop3456",
"productTitle": "Exemplo Produto",
"price": 199.90
}Al superar el límite, el backend responde con HTTP 429. Los límites son por API Key + ruta y se reinician en la ventana indicada.
Valores posibles del campo status en el recurso Transaction (GET /transactions/:id)
PIX generado, esperando pago. El saldo aún no fue movido.
Eulen procesando. Puede estar en under_review (anti-fraude), delayed (Eulen reteniendo el DePix antes del approved — sin efecto financiero todavía), o en delay-depix HOLD después del approved (PIX confirmado y blockedBalance ya incrementado; sub-fase identificada por metadata.holdSource = "delay-depix" + metadata.approvedAt + availableAt). Webhook de esta sub-fase: deposit.approved_delay.
DePix entregado a la reserva. Saldo acreditado en balance (o en pendingBalance si isOnHold=true). Webhook emitido: deposit.success o deposit.on_hold.
PIX falló (no pagado, expirado, rechazado por Eulen o banco). Sin movimiento de saldo.
Transacción cancelada manualmente (admin) o por TTL (PIX no pagado dentro de la ventana).
Reembolso: MED (devolución PIX) detectado durante la retención o reembolso manual hecho por admin. Saldo retirado de blockedBalance o pendingBalance según metadata.holdSource.
Transacción bajo revisión manual del equipo. Esperando aprobación o rechazo.
Recibe notificaciones automáticas cuando ocurran eventos importantes
Cada webhook trae una firma HMAC-SHA256 en el header X-Webhook-Signature, calculada sobre el cuerpo crudo (raw body) con el secreto del webhook. Valida siempre antes de procesar.
X-Webhook-Signature: a1b2c3d4e5f6...payment.successPago confirmado y acreditado.
{
"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.createdCompra creada, esperando pago.
{
"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.grantedAcceso liberado al comprador tras la confirmación del pago.
{
"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_medPago reembolsado por MED (contracargo) durante la retención 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.refundedDepósito reembolsado por Eulen al pagador (PIX devuelto); el importe retenido se revierte. Un reembolso MED de fondos ya acreditados llega como payment.refunded_med. El campo opcional refundReasonDetail trae el motivo inferido (med_chargeback, cpf_mismatch, cnpj_mismatch, spam_like, user_blocked, payer_refused, indeterminate_payer). Salvo en indeterminate_payer, el payload incluye payerEuid — id seudónimo y estable de la cuenta pagadora, para correlacionar reincidentes sin exponer identidad. En los motivos mismatch van también payerName y payerTaxNumber, con el CPF siempre enmascarado.
{
"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_pendingAviso PREVIO: Eulen anunció que va a devolver un PIX ya recibido (riesgo/compliance). No es el evento terminal — payment.refunded llega después, en promedio ~2h y hasta 3 días cuando el proveedor retiene el importe antes (informado en expectedRefundAt). Trátalo como irreversible. El campo opcional rejectionReasons trae el código crudo del proveedor (PAYER_MISMATCH, BLOCKED_USER); el vocabulario es suyo y puede crecer — trata un código desconocido como rechazo genérico de riesgo. Si el comprador ya tenía acceso, OrionPay lo revoca y envía accessRevoked: true; revierte la entrega de tu lado también.
{
"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 (contracargo) detectado en un depósito 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.completedRetiro transmitido a la blockchain o al banco.
{
"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.settledRetiro totalmente confirmado en la red de destino (incluye Peg-Out BTC).
{
"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.failedEl retiro falló (saldo insuficiente, dirección inválida, etc). Los importes ya volvieron al saldo.
{
"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 atómico ejecutado con éxito.
{
"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.failedEl swap falló o fue reembolsado.
{
"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.testEvento disparado manualmente desde el panel para probar la URL.
{
"event": "webhook.test",
"timestamp": "2026-04-24T14:05:00.000Z",
"data": {
"message": "Webhook de teste",
"testId": "test_abc123"
}
}// Validar firma de webhook (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 })
})