{"openapi":"3.1.0","info":{"title":"OrionPay API","version":"1.0.0","summary":"Payments, withdrawals, and crypto for hospitals and digital products.","description":"OrionPay is a Brazilian payment platform for hospitals, digital products, and crypto. This spec covers every endpoint exposed via API key authentication (the \"API & WebHooks\" panel). For LLM agents: see https://pay.orion.moe/llms.txt and the Streamable HTTP MCP server at https://payapi.orion.moe/mcp.","contact":{"name":"OrionPay","url":"https://pay.orion.moe"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://payapi.orion.moe","description":"Production"}],"tags":[{"name":"PIX","description":"Brazilian instant payments (BRL → DePix)."},{"name":"Integration","description":"Buyer and product access management."},{"name":"Products","description":"Create a digital product together with its product API key and optional webhook in one call. Requires personal API key (productId=null)."},{"name":"Personal","description":"Personal API: balance, withdrawals, swaps. Requires personal API key (productId=null)."},{"name":"Webhooks","description":"Outbound HTTPS notifications, signed in the X-Webhook-Signature header (hex HMAC-SHA256 of the raw body)."}],"paths":{"/api/v1/pix/generate":{"post":{"operationId":"pix_generate","summary":"Generate PIX for product payment","description":"Generates a PIX QR code for a product purchase. The product is identified by the API key (product key). On payment, a webhook is delivered (`payment.success`) and the buyer receives an access token. Payer identification (cpf OR euid) is required since 2026-06-30 for totals ≥ R$10.00; totals strictly below R$10.00 are exempt since 2026-07-21.","tags":["PIX"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"api-key","x-mcp-tool":true,"x-mcp-tool-name":"pix_generate","x-rate-limit":{"limit":0,"windowMs":0,"note":"No rate limit (critical payment generation)."},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PixResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeneratePixRequest"}}}}}},"/api/v1/pix/status/{id}":{"get":{"operationId":"pix_status","summary":"Check PIX payment status","description":"Polls the status of a PIX payment. The `id` parameter accepts purchaseId (numeric), transactionId (numeric), or eulenDepositId (UUID). Webhook delivery is preferred over polling.","tags":["PIX"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"api-key","x-mcp-tool":true,"x-mcp-tool-name":"pix_status","x-rate-limit":{"limit":60,"windowMs":60000,"note":"60 requests per minute per ID per API key."},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentStatusResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"purchaseId, transactionId, or eulenDepositId (UUID)."}]}},"/integration/verify-access":{"get":{"operationId":"integration_verify_access","summary":"Verify buyer access to product","description":"Checks whether a buyer (by email or accessToken) has paid access to the product associated with the API key.","tags":["Integration"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"api-key","x-mcp-tool":true,"x-mcp-tool-name":"integration_verify_access","x-rate-limit":{"limit":60,"windowMs":60000,"note":"60 requests per minute per token/email per API key."},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyAccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"email","in":"query","schema":{"type":"string","format":"email"}},{"name":"accessToken","in":"query","schema":{"type":"string"}}]}},"/integration/buyers":{"get":{"operationId":"integration_list_buyers","summary":"List product buyers","description":"Returns all buyers of the product associated with the API key. Filter by status optional.","tags":["Integration"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"api-key","x-mcp-tool":true,"x-mcp-tool-name":"integration_list_buyers","x-rate-limit":{"limit":60,"windowMs":60000},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Buyer"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["PENDING","PAID","EXPIRED","CANCELED","REFUNDED"]}}]}},"/integration/buyers/{purchaseId}":{"get":{"operationId":"integration_get_buyer","summary":"Get buyer details","description":"Returns details for a specific purchase belonging to the API key's product.","tags":["Integration"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"api-key","x-mcp-tool":true,"x-mcp-tool-name":"integration_get_buyer","x-rate-limit":{"limit":60,"windowMs":60000},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Buyer"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"purchaseId","in":"path","required":true,"schema":{"type":"integer"}}]}},"/integration/content":{"get":{"operationId":"integration_get_content","summary":"Get product content for authenticated buyer","description":"Returns the product content (files, lessons, etc.) accessible to the buyer who owns the supplied accessToken.","tags":["Integration"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"api-key","x-mcp-tool":true,"x-mcp-tool-name":"integration_get_content","x-rate-limit":{"limit":60,"windowMs":60000},"responses":{"200":{"description":"OK"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"accessToken","in":"query","required":true,"schema":{"type":"string"}}]}},"/integration/grant-access":{"post":{"operationId":"integration_grant_access","summary":"Manually grant access to a buyer","description":"Grants access to a buyer without a payment (e.g. for refunds resolution, comp tickets, or migrations). An access token is generated.","tags":["Integration"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"api-key","x-mcp-tool":true,"x-mcp-tool-name":"integration_grant_access","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Buyer"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantAccessRequest"}}}}}},"/integration/revoke-access/{purchaseId}":{"delete":{"operationId":"integration_revoke_access","summary":"Revoke buyer access","description":"Revokes a buyer's access. The access token is invalidated.","tags":["Integration"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"api-key","x-mcp-tool":true,"x-mcp-tool-name":"integration_revoke_access","responses":{"200":{"description":"OK"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"purchaseId","in":"path","required":true,"schema":{"type":"integer"}}]}},"/api/v1/integration/deposit/pix":{"post":{"operationId":"integration_deposit_pix","summary":"Generate PIX deposit (with payer CPF)","description":"Generates a PIX QR code for a product deposit. The payer's CPF (or euid) is REQUIRED since 2026-06-30 for amounts ≥ R$10.00 (provider enforcement; amounts strictly below R$10.00 are exempt since 2026-07-21); requests with neither, when required, get 400. The merchant's identification (merchantId) is resolved automatically from the API key owner and must not be sent.","tags":["Integration"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"api-key","x-mcp-tool":true,"x-mcp-tool-name":"integration_deposit_pix","x-rate-limit":{"limit":10,"windowMs":60000},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PixResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePixDepositRequest"}}}}}},"/api/v1/personal/products":{"post":{"operationId":"personal_create_product","summary":"Create a product with its API key and webhook","description":"Creates a NEW digital product in one call and returns everything needed to start selling: the product, its checkout link, a product API key and, optionally, a signed webhook. Requires the PERSONAL API key (productId=null); a product key gets 403 PERSONAL_KEY_REQUIRED. The returned `apiKey.key` is the product key that POST /api/v1/pix/generate (MCP `pix_generate`), pix/status and the integration endpoints use. All-or-nothing: if a step fails, the partially created product is removed.\n\nBody (JSON). Any unknown key, at any level (e.g. `metadata`), is rejected with 400 INVALID_FIELD naming the field:\n- `title` (required): string, 1–191 chars (after trimming).\n- `productType` (required): CURSO_ONLINE | FOTOS | ARQUIVOS | AREA_MEMBROS | TELEGRAM_CHANNEL | CONSULTORIA | MENTORIA.\n- `description`: string, up to 5000 chars.\n- `price`: number ≥ 0, at most 2 decimals. 0 or absent = open price: each pix/generate call sets `amount` and no checkout link is created. Above 0 it must reach the platform minimum (400 MIN_PIX_ORDER_AMOUNT).\n- `hideFromMarketplace`: boolean, default true.\n- `publish`: boolean, default true. pix/generate only works on a PUBLISHED product; false leaves it DRAFT.\n- `externalReference`: string, 1–100 chars of [A-Za-z0-9._:-]. Your idempotency key per account; not a secret.\n- `delivery`: only for AREA_MEMBROS → {`accessUrl` (required, https, ≤2048), `accessInstructions` (≤5000)}; only for TELEGRAM_CHANNEL → {`channelLink` (required, https, ≤2048), `joinInstructions` (≤5000)}. Any other type → 400.\n- `webhook`: {`url` (required, public https URL with a domain name, no IP), `events` (non-empty array of purchase.created, payment.received, payment.success, access.granted, payment.under_review, payment.refund_pending, payment.refunded, payment.refunded_med, med.created, or [\"*\"]; default [\"purchase.created\",\"access.granted\",\"payment.success\"]), `name` (1–255, default \"Default\"), `secret` (32–128 chars; a 64-char hex secret is generated when omitted)}.\n\nResponse 201: {success: true, replayed: false, product: {id, title, description, productType, price, status, hideFromMarketplace, externalReference, createdAt}, paymentLink: {slug, url} or null (null when price is 0/absent or the product is not PUBLISHED), apiKey: {id, key, name, createdAt} (null only on replay, see below), webhook: {id, name, url, events, isActive, secret} or null}. Store `apiKey.key` and `webhook.secret` as secrets. Deliveries carry `X-Webhook-Signature` = lowercase hex HMAC-SHA256 of the raw body, keyed with `webhook.secret`.\n\nRetry: the same `externalReference` (case-sensitive) again returns 200 with replayed: true and the stored product, link, key and webhook, completing any missing step (key, requested webhook, publication; it never unpublishes). apiKey is null on replay when the product key was disabled or expired in the dashboard — manage it there. 409 PRODUCT_REFERENCE_EXISTS (body includes productId) only when the stored productType differs, or a different webhook.url is requested for a product that already has a webhook.\n\nErrors: match on the `code` field; the message is Portuguese, starts with \"API:\" and ends with the code in parentheses. 403 PERSONAL_KEY_REQUIRED, PERSONAL_API_DISABLED, OFFICIAL_STORE_ACCOUNT, BUSINESS_NOT_ACTIVATED, BUSINESS_EXPIRED. 400 INVALID_FIELD (with `field`), MIN_PIX_ORDER_AMOUNT. 409 PRODUCT_REFERENCE_EXISTS. 429 API_PRODUCT_DAILY_LIMIT (by default 20 products per account in a rolling 24h, any channel), PROVISIONING_IN_PROGRESS (another call for the same account is running; retry shortly), or the request throttle (10/hour per key on REST; MCP has no request throttle, but the daily cap applies).\n\nNot available via API (dashboard only): images and content (lessons, files), editing or deleting, RIFA/PRODUTO_FISICO/FUNDING. CURSO_ONLINE/FOTOS/ARQUIVOS stay empty until content is uploaded in the dashboard; CONSULTORIA/MENTORIA are delivered outside OrionPay. For pix/generate sales OrionPay sends the buyer nothing: deliver yourself, e.g. link to https://pay.orion.moe/acesso/<accessToken> (token from access.granted / payment.success or pix/status). The account owner receives a security e-mail for every product created. Revoking the personal key does NOT revoke product keys created with it.","tags":["Products"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"personal-api-key","x-mcp-tool":true,"x-mcp-tool-name":"personal_create_product","x-rate-limit":{"limit":10,"windowMs":3600000,"note":"Per API key, REST only (MCP has no request throttle). Separately, each account can create at most 20 products (default) in a rolling 24h via any channel, REST and MCP included."},"responses":{"200":{"description":"Replay — a product with this externalReference already exists for the account (replayed: true); missing steps were completed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductResponse"}}}},"201":{"description":"Created — product, product API key and (optional) webhook provisioned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductResponse"}}}},"400":{"description":"INVALID_FIELD (with `field`) or MIN_PIX_ORDER_AMOUNT.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"PERSONAL_KEY_REQUIRED, PERSONAL_API_DISABLED, OFFICIAL_STORE_ACCOUNT, BUSINESS_NOT_ACTIVATED or BUSINESS_EXPIRED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductErrorResponse"}}}},"409":{"description":"PRODUCT_REFERENCE_EXISTS — the externalReference belongs to a product with another productType or another webhook URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductErrorResponse"}}}},"429":{"description":"API_PRODUCT_DAILY_LIMIT or PROVISIONING_IN_PROGRESS (with `code`), or the standard request throttle (10/hour per key, without `code`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductRequest"}}}}}},"/api/v1/personal/balance":{"get":{"operationId":"personal_balance","summary":"Get all wallet balances","description":"Returns BRL balance and all crypto balances (Liquid Network, Bitcoin, Polygon, etc.).","tags":["Personal"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"personal-api-key","x-mcp-tool":true,"x-mcp-tool-name":"personal_balance","x-rate-limit":{"limit":60,"windowMs":3600000},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BalanceResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/personal/limits":{"get":{"operationId":"personal_limits","summary":"Get withdrawal limits","description":"Returns the user's daily/monthly/per-transaction withdrawal limits and remaining usage.","tags":["Personal"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"personal-api-key","x-mcp-tool":true,"x-mcp-tool-name":"personal_limits","x-rate-limit":{"limit":30,"windowMs":3600000},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawLimitsResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/personal/transactions":{"get":{"operationId":"personal_transactions","summary":"List transaction history","description":"Returns the user's transaction history (deposits, withdrawals, swaps), most recent first.","tags":["Personal"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"personal-api-key","x-mcp-tool":true,"x-mcp-tool-name":"personal_transactions","x-rate-limit":{"limit":60,"windowMs":3600000},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":10,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}},{"name":"type","in":"query","schema":{"type":"string","enum":["DEPOSIT","WITHDRAWAL","SWAP"]}}]}},"/api/v1/personal/withdraw/quote":{"get":{"operationId":"personal_withdraw_quote","summary":"Quote a PIX withdrawal","description":"Previews a PIX withdrawal without moving money: pass exactly one of `amount` (gross wallet debit → returns the net payout) or `netAmount` (desired payout → returns the gross debit). Uses the same math as POST /api/v1/personal/withdraw/pix, so the quote matches what gets charged. Sending both or neither returns 400. Out-of-limit amounts still return 200 with validation.withinLimits=false and a reason.","tags":["Personal"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"personal-api-key","x-mcp-tool":false,"x-rate-limit":{"limit":120,"windowMs":3600000},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PixWithdrawQuoteResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"amount","in":"query","schema":{"type":"number","minimum":1,"maximum":100000},"description":"Gross BRL debit. Mutually exclusive with netAmount."},{"name":"netAmount","in":"query","schema":{"type":"number","minimum":1,"maximum":100000},"description":"Desired BRL payout to the recipient. Mutually exclusive with amount."}]}},"/api/v1/personal/withdraw/pix":{"post":{"operationId":"personal_withdraw_pix","summary":"Queue a PIX withdrawal","description":"Queues a PIX withdrawal from the user's BRL wallet. Send exactly one of `amount` (gross debit, fees included) or `netAmount` (amount the recipient must receive); preview with GET /api/v1/personal/withdraw/quote. Requires API withdrawals to be enabled (see /enable-withdrawals). A 7-day hold applies to deposits while API withdrawals are enabled.","tags":["Personal"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"personal-api-key","x-mcp-tool":true,"x-mcp-tool-name":"personal_withdraw_pix","x-rate-limit":{"limit":10,"windowMs":3600000},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawalQueuedResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PixWithdrawRequest"}}}}}},"/api/v1/personal/withdraw/crypto":{"post":{"operationId":"personal_withdraw_crypto","summary":"Queue a crypto withdrawal","description":"Queues a crypto withdrawal. Supports L-BTC, BTC-PEGOUT (Bitcoin mainnet via peg-out), DePix, USDt, USDC. Network parameter routes via SideShift for cross-chain.","tags":["Personal"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"personal-api-key","x-mcp-tool":true,"x-mcp-tool-name":"personal_withdraw_crypto","x-rate-limit":{"limit":10,"windowMs":3600000},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawalQueuedResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CryptoWithdrawRequest"}}}}}},"/api/v1/personal/withdraw/status/{id}":{"get":{"operationId":"personal_withdraw_status","summary":"Get withdrawal status","description":"Returns the status, settlement details, and txHash of a withdrawal by transaction ID.","tags":["Personal"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"personal-api-key","x-mcp-tool":true,"x-mcp-tool-name":"personal_withdraw_status","x-rate-limit":{"limit":120,"windowMs":3600000},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawalStatusResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}]}},"/api/v1/personal/swap/quote":{"post":{"operationId":"personal_swap_quote","summary":"Get a swap quote","description":"Returns a quote for swapping between supported assets (DePix, L-BTC, USDt, EURx). Quote is valid for ~30 seconds.","tags":["Personal"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"personal-api-key","x-mcp-tool":true,"x-mcp-tool-name":"personal_swap_quote","x-rate-limit":{"limit":120,"windowMs":3600000},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SwapQuoteResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SwapRequest"}}}}}},"/api/v1/personal/swap/execute":{"post":{"operationId":"personal_swap_execute","summary":"Execute a swap","description":"Executes a swap. Re-quotes internally before locking funds; if the rate moved beyond tolerance the swap is rejected.","tags":["Personal"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"personal-api-key","x-mcp-tool":true,"x-mcp-tool-name":"personal_swap_execute","x-rate-limit":{"limit":20,"windowMs":3600000},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SwapExecuteResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SwapRequest"}}}}}},"/api/v1/personal/withdraw-enabled":{"get":{"operationId":"personal_withdraw_enabled","summary":"Check API withdrawals enabled","description":"Returns whether API-driven withdrawals are enabled and the deposit hold period.","tags":["Personal"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"personal-api-key","x-mcp-tool":true,"x-mcp-tool-name":"personal_withdraw_enabled","x-rate-limit":{"limit":60,"windowMs":3600000},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawEnabledResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/personal/enable-withdrawals":{"post":{"operationId":"personal_enable_withdrawals","summary":"Enable API withdrawals","description":"Enables withdrawal endpoints for this account. Imposes a 7-day hold on every deposit thereafter (anti-fraud). Requires explicit acceptance of the terms.","tags":["Personal"],"security":[{"ApiKey":[]},{"BearerApiKey":[]}],"x-auth":"personal-api-key","x-mcp-tool":false,"x-rate-limit":{"limit":5,"windowMs":3600000},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawEnabledResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnableWithdrawalsRequest"}}}}}}},"webhooks":{"payment.success":{"post":{"summary":"Webhook: payment.success","description":"OrionPay delivers webhooks via HTTPS POST to the URL registered for the webhook — configured in the dashboard (/dashboard/api-webhooks) or via POST /api/v1/personal/products.\n\n**Headers** sent on every delivery:\n- `Content-Type: application/json`\n- `X-Webhook-Signature: <hex>` — lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No `sha256=` prefix. Absent when the webhook has no secret.\n\nThere are no `X-OrionPay-*` headers.\n\n**Body** — every event uses the same envelope; the event-specific fields documented per event live under `data`:\n```json\n{\n  \"event\": \"payment.success\",\n  \"productId\": 123,\n  \"productTitle\": \"Curso X\",\n  \"webhookName\": \"Default\",\n  \"timestamp\": \"2026-09-15T12:00:00.000Z\",\n  \"data\": { \"...\": \"event-specific fields\" }\n}\n```\n`productId` and `productTitle` are null for account-level (personal) webhooks.\n\n**Verification** (Node.js / Express) — compute the HMAC over the raw bytes received; re-serialized JSON is not guaranteed to match:\n```js\nconst crypto = require('crypto');\napp.post('/webhooks/orionpay', express.raw({ type: 'application/json' }), (req, res) => {\n  const received = Buffer.from(req.get('X-Webhook-Signature') || '', 'utf8');\n  const expected = Buffer.from(\n    crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'), 'utf8');\n  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {\n    return res.sendStatus(401);\n  }\n  const { event, data } = JSON.parse(req.body.toString('utf8'));\n  // handle event...\n  res.sendStatus(200);\n});\n```\n\n**Retries**: up to 8 attempts with exponential backoff starting at ~10s; each attempt times out after 10s. Only a 2xx response counts as delivered.\n**Idempotency**: the same event can be delivered more than once — deduplicate on `event` + `data.transactionId` (or `data.purchaseId` when there is no transaction id).","tags":["Webhooks"],"parameters":[{"name":"X-Webhook-Signature","in":"header","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No prefix. Absent when the webhook has no secret."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","productId","productTitle","webhookName","timestamp","data"],"properties":{"event":{"type":"string","const":"payment.success"},"productId":{"type":"integer","nullable":true,"description":"Product the webhook belongs to; null for account-level (personal) webhooks."},"productTitle":{"type":"string","nullable":true,"description":"Product title; null for account-level (personal) webhooks."},"webhookName":{"type":"string","description":"Name given to the webhook."},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 time the delivery was created."},"data":{"type":"object","properties":{"purchaseId":{"type":"integer"},"transactionId":{"type":"string","nullable":true,"description":"PIX charge id (the id returned by pix/generate and accepted by pix/status). Dashboard test deliveries send a mock id (`mock_<hex>`)."},"productId":{"type":"integer"},"pixCode":{"type":"string","nullable":true},"buyerEmail":{"type":"string","format":"email"},"buyerName":{"type":"string","nullable":true},"accessToken":{"type":"string","description":"Buyer access token (https://pay.orion.moe/acesso/<accessToken>)."},"price":{"type":"number","description":"Gross sale amount in BRL."},"netAmount":{"type":"number","description":"Amount after fees, in BRL. May be absent on resends."},"distributions":{"type":"array","description":"Revenue split among the producer and co-producers. May be absent on resends.","items":{"type":"object","properties":{"userId":{"type":"integer"},"userName":{"type":"string"},"percentage":{"type":"number"},"amount":{"type":"number"}}}},"isResend":{"type":"boolean","description":"Present (true) when the delivery was resent from the dashboard."},"paidAt":{"type":"string","format":"date-time","description":"Present only on status-only reconciliation deliveries."},"timestamp":{"type":"string","format":"date-time"}},"description":"Event-specific payload."}}}}}},"responses":{"2XX":{"description":"ACK — delivery counted as successful."},"4XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."},"5XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."}}}},"purchase.created":{"post":{"summary":"Webhook: purchase.created","description":"OrionPay delivers webhooks via HTTPS POST to the URL registered for the webhook — configured in the dashboard (/dashboard/api-webhooks) or via POST /api/v1/personal/products.\n\n**Headers** sent on every delivery:\n- `Content-Type: application/json`\n- `X-Webhook-Signature: <hex>` — lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No `sha256=` prefix. Absent when the webhook has no secret.\n\nThere are no `X-OrionPay-*` headers.\n\n**Body** — every event uses the same envelope; the event-specific fields documented per event live under `data`:\n```json\n{\n  \"event\": \"payment.success\",\n  \"productId\": 123,\n  \"productTitle\": \"Curso X\",\n  \"webhookName\": \"Default\",\n  \"timestamp\": \"2026-09-15T12:00:00.000Z\",\n  \"data\": { \"...\": \"event-specific fields\" }\n}\n```\n`productId` and `productTitle` are null for account-level (personal) webhooks.\n\n**Verification** (Node.js / Express) — compute the HMAC over the raw bytes received; re-serialized JSON is not guaranteed to match:\n```js\nconst crypto = require('crypto');\napp.post('/webhooks/orionpay', express.raw({ type: 'application/json' }), (req, res) => {\n  const received = Buffer.from(req.get('X-Webhook-Signature') || '', 'utf8');\n  const expected = Buffer.from(\n    crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'), 'utf8');\n  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {\n    return res.sendStatus(401);\n  }\n  const { event, data } = JSON.parse(req.body.toString('utf8'));\n  // handle event...\n  res.sendStatus(200);\n});\n```\n\n**Retries**: up to 8 attempts with exponential backoff starting at ~10s; each attempt times out after 10s. Only a 2xx response counts as delivered.\n**Idempotency**: the same event can be delivered more than once — deduplicate on `event` + `data.transactionId` (or `data.purchaseId` when there is no transaction id).","tags":["Webhooks"],"parameters":[{"name":"X-Webhook-Signature","in":"header","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No prefix. Absent when the webhook has no secret."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","productId","productTitle","webhookName","timestamp","data"],"properties":{"event":{"type":"string","const":"purchase.created"},"productId":{"type":"integer","nullable":true,"description":"Product the webhook belongs to; null for account-level (personal) webhooks."},"productTitle":{"type":"string","nullable":true,"description":"Product title; null for account-level (personal) webhooks."},"webhookName":{"type":"string","description":"Name given to the webhook."},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 time the delivery was created."},"data":{"type":"object","properties":{"purchaseId":{"type":"integer"},"transactionId":{"type":"string","description":"PIX charge id of the purchase. Absent on order-bump purchases."},"amount":{"type":"number","description":"BRL."},"status":{"type":"string","enum":["pending","paid"],"description":"pending for a new PIX charge; paid only for order-bump purchases created together with an already paid main purchase."},"createdAt":{"type":"string","format":"date-time"},"buyerEmail":{"type":"string","format":"email","nullable":true},"buyerName":{"type":"string","nullable":true},"isOrderBump":{"type":"boolean","description":"Present (true) on order-bump purchases."},"parentPurchaseId":{"type":"integer","description":"Order-bump purchases only: the main purchase."}},"description":"Event-specific payload."}}}}}},"responses":{"2XX":{"description":"ACK — delivery counted as successful."},"4XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."},"5XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."}}}},"access.granted":{"post":{"summary":"Webhook: access.granted","description":"OrionPay delivers webhooks via HTTPS POST to the URL registered for the webhook — configured in the dashboard (/dashboard/api-webhooks) or via POST /api/v1/personal/products.\n\n**Headers** sent on every delivery:\n- `Content-Type: application/json`\n- `X-Webhook-Signature: <hex>` — lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No `sha256=` prefix. Absent when the webhook has no secret.\n\nThere are no `X-OrionPay-*` headers.\n\n**Body** — every event uses the same envelope; the event-specific fields documented per event live under `data`:\n```json\n{\n  \"event\": \"payment.success\",\n  \"productId\": 123,\n  \"productTitle\": \"Curso X\",\n  \"webhookName\": \"Default\",\n  \"timestamp\": \"2026-09-15T12:00:00.000Z\",\n  \"data\": { \"...\": \"event-specific fields\" }\n}\n```\n`productId` and `productTitle` are null for account-level (personal) webhooks.\n\n**Verification** (Node.js / Express) — compute the HMAC over the raw bytes received; re-serialized JSON is not guaranteed to match:\n```js\nconst crypto = require('crypto');\napp.post('/webhooks/orionpay', express.raw({ type: 'application/json' }), (req, res) => {\n  const received = Buffer.from(req.get('X-Webhook-Signature') || '', 'utf8');\n  const expected = Buffer.from(\n    crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'), 'utf8');\n  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {\n    return res.sendStatus(401);\n  }\n  const { event, data } = JSON.parse(req.body.toString('utf8'));\n  // handle event...\n  res.sendStatus(200);\n});\n```\n\n**Retries**: up to 8 attempts with exponential backoff starting at ~10s; each attempt times out after 10s. Only a 2xx response counts as delivered.\n**Idempotency**: the same event can be delivered more than once — deduplicate on `event` + `data.transactionId` (or `data.purchaseId` when there is no transaction id).","tags":["Webhooks"],"parameters":[{"name":"X-Webhook-Signature","in":"header","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No prefix. Absent when the webhook has no secret."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","productId","productTitle","webhookName","timestamp","data"],"properties":{"event":{"type":"string","const":"access.granted"},"productId":{"type":"integer","nullable":true,"description":"Product the webhook belongs to; null for account-level (personal) webhooks."},"productTitle":{"type":"string","nullable":true,"description":"Product title; null for account-level (personal) webhooks."},"webhookName":{"type":"string","description":"Name given to the webhook."},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 time the delivery was created."},"data":{"type":"object","properties":{"purchaseId":{"type":"integer"},"buyerEmail":{"type":"string","format":"email"},"accessToken":{"type":"string"},"expiresAt":{"type":"string","format":"date-time","nullable":true},"grantedAt":{"type":"string","format":"date-time"},"isResend":{"type":"boolean","description":"Present (true) when the delivery was resent from the dashboard."}},"description":"Event-specific payload."}}}}}},"responses":{"2XX":{"description":"ACK — delivery counted as successful."},"4XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."},"5XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."}}}},"payment.refunded_med":{"post":{"summary":"Webhook: payment.refunded_med","description":"OrionPay delivers webhooks via HTTPS POST to the URL registered for the webhook — configured in the dashboard (/dashboard/api-webhooks) or via POST /api/v1/personal/products.\n\n**Headers** sent on every delivery:\n- `Content-Type: application/json`\n- `X-Webhook-Signature: <hex>` — lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No `sha256=` prefix. Absent when the webhook has no secret.\n\nThere are no `X-OrionPay-*` headers.\n\n**Body** — every event uses the same envelope; the event-specific fields documented per event live under `data`:\n```json\n{\n  \"event\": \"payment.success\",\n  \"productId\": 123,\n  \"productTitle\": \"Curso X\",\n  \"webhookName\": \"Default\",\n  \"timestamp\": \"2026-09-15T12:00:00.000Z\",\n  \"data\": { \"...\": \"event-specific fields\" }\n}\n```\n`productId` and `productTitle` are null for account-level (personal) webhooks.\n\n**Verification** (Node.js / Express) — compute the HMAC over the raw bytes received; re-serialized JSON is not guaranteed to match:\n```js\nconst crypto = require('crypto');\napp.post('/webhooks/orionpay', express.raw({ type: 'application/json' }), (req, res) => {\n  const received = Buffer.from(req.get('X-Webhook-Signature') || '', 'utf8');\n  const expected = Buffer.from(\n    crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'), 'utf8');\n  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {\n    return res.sendStatus(401);\n  }\n  const { event, data } = JSON.parse(req.body.toString('utf8'));\n  // handle event...\n  res.sendStatus(200);\n});\n```\n\n**Retries**: up to 8 attempts with exponential backoff starting at ~10s; each attempt times out after 10s. Only a 2xx response counts as delivered.\n**Idempotency**: the same event can be delivered more than once — deduplicate on `event` + `data.transactionId` (or `data.purchaseId` when there is no transaction id).","tags":["Webhooks"],"parameters":[{"name":"X-Webhook-Signature","in":"header","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No prefix. Absent when the webhook has no secret."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","productId","productTitle","webhookName","timestamp","data"],"properties":{"event":{"type":"string","const":"payment.refunded_med"},"productId":{"type":"integer","nullable":true,"description":"Product the webhook belongs to; null for account-level (personal) webhooks."},"productTitle":{"type":"string","nullable":true,"description":"Product title; null for account-level (personal) webhooks."},"webhookName":{"type":"string","description":"Name given to the webhook."},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 time the delivery was created."},"data":{"type":"object","properties":{"transactionId":{"type":"integer","description":"Resends from the dashboard send the PIX charge id (string) instead."},"amount":{"type":"number"},"reference":{"type":"string","nullable":true},"status":{"type":"string","enum":["refunded"]},"type":{"type":"string","example":"PAYMENT"},"currency":{"type":"string","enum":["BRL"]},"refundedAt":{"type":"string","format":"date-time"},"medDetectedAt":{"type":"string","format":"date-time"},"reason":{"type":"string","enum":["med_during_antifraud_hold","admin_block","admin_resend"],"description":"Why OrionPay reversed the held amount: a MED filed during the hold, an administrative block, or a dashboard resend."},"payerEuid":{"type":"string","description":"Pseudonymous Eulen end-user id of the paying account, when known."},"purchaseId":{"type":"integer","description":"Present only for product checkout payments."},"productId":{"type":"integer","description":"Present only for product checkout payments."},"productTitle":{"type":"string","description":"Present only for product checkout payments."},"buyerEmail":{"type":"string","format":"email","description":"Present only for product checkout payments."},"buyerName":{"type":"string","nullable":true,"description":"Present only for product checkout payments."},"refundReasonDetail":{"type":"string","enum":["med_chargeback"],"description":"Always med_chargeback when present — the refund was triggered by the MED the payer filed."},"timestamp":{"type":"string","format":"date-time"}},"description":"Event-specific payload."}}}}}},"responses":{"2XX":{"description":"ACK — delivery counted as successful."},"4XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."},"5XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."}}}},"payment.refunded":{"post":{"summary":"Webhook: payment.refunded","description":"OrionPay delivers webhooks via HTTPS POST to the URL registered for the webhook — configured in the dashboard (/dashboard/api-webhooks) or via POST /api/v1/personal/products.\n\n**Headers** sent on every delivery:\n- `Content-Type: application/json`\n- `X-Webhook-Signature: <hex>` — lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No `sha256=` prefix. Absent when the webhook has no secret.\n\nThere are no `X-OrionPay-*` headers.\n\n**Body** — every event uses the same envelope; the event-specific fields documented per event live under `data`:\n```json\n{\n  \"event\": \"payment.success\",\n  \"productId\": 123,\n  \"productTitle\": \"Curso X\",\n  \"webhookName\": \"Default\",\n  \"timestamp\": \"2026-09-15T12:00:00.000Z\",\n  \"data\": { \"...\": \"event-specific fields\" }\n}\n```\n`productId` and `productTitle` are null for account-level (personal) webhooks.\n\n**Verification** (Node.js / Express) — compute the HMAC over the raw bytes received; re-serialized JSON is not guaranteed to match:\n```js\nconst crypto = require('crypto');\napp.post('/webhooks/orionpay', express.raw({ type: 'application/json' }), (req, res) => {\n  const received = Buffer.from(req.get('X-Webhook-Signature') || '', 'utf8');\n  const expected = Buffer.from(\n    crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'), 'utf8');\n  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {\n    return res.sendStatus(401);\n  }\n  const { event, data } = JSON.parse(req.body.toString('utf8'));\n  // handle event...\n  res.sendStatus(200);\n});\n```\n\n**Retries**: up to 8 attempts with exponential backoff starting at ~10s; each attempt times out after 10s. Only a 2xx response counts as delivered.\n**Idempotency**: the same event can be delivered more than once — deduplicate on `event` + `data.transactionId` (or `data.purchaseId` when there is no transaction id).","tags":["Webhooks"],"parameters":[{"name":"X-Webhook-Signature","in":"header","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No prefix. Absent when the webhook has no secret."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","productId","productTitle","webhookName","timestamp","data"],"properties":{"event":{"type":"string","const":"payment.refunded"},"productId":{"type":"integer","nullable":true,"description":"Product the webhook belongs to; null for account-level (personal) webhooks."},"productTitle":{"type":"string","nullable":true,"description":"Product title; null for account-level (personal) webhooks."},"webhookName":{"type":"string","description":"Name given to the webhook."},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 time the delivery was created."},"data":{"type":"object","properties":{"transactionId":{"type":"integer","description":"Dashboard test deliveries send a mock string id (`mock_<uuid>`) instead."},"amount":{"type":"number"},"reference":{"type":"string","nullable":true},"status":{"type":"string","enum":["refunded"]},"type":{"type":"string","example":"DEPOSIT"},"currency":{"type":"string","enum":["BRL"]},"reason":{"type":"string","enum":["eulen_refund"],"description":"PIX-provider refund. When the refunded payment carries a MED dispute, refundReasonDetail says med_chargeback (MED reversals of already-credited held amounts still fire the dedicated payment.refunded_med event)."},"refundReasonDetail":{"type":"string","enum":["cpf_mismatch","cnpj_mismatch","med_chargeback","spam_like","user_blocked","payer_refused","indeterminate_payer","no_typed_cpf","cpf_exempt","no_payer_data"],"description":"Optional. Inferred via a cascade of checks: med_chargeback (the payer filed a MED — Bacen's fraud-dispute mechanism — and the payment was returned during the hold window) → document cross-check (diverging CPF → cpf_mismatch, diverging CNPJ → cnpj_mismatch; never applies below the CPF-exemption amount: since 06/08/2026 a document typed for a total strictly under R$10.00 is validated and recorded but NOT forwarded to the provider, so the QR is not locked to it and any account can pay — mismatch refunds cannot happen there) → spam_like (provider anti-spam: 2 or more completed deposits from the same payer in the ~30 minutes before the refunded one — refused attempts themselves do not count) → user_blocked (payer blocked at the provider) → payer_refused (catch-all: provider risk engine refused, no reason declared). Between 28/07/2026 21:39 UTC and 01/08/2026 01:07 UTC the provider did not disclose the paying account on refunds (its refund pipeline echoed back the document the QR was locked to); refunds in that closed window classify as indeterminate_payer and carry no payer fields at all. The real payer identity has been disclosed again since 01/08/2026, so refunds from then on classify normally. For the mismatch reasons only, the payload also carries payerName/payerTaxNumber so the merchant can compare against the document typed at checkout; for the other reasons (except indeterminate_payer) the payload carries only this enum plus payerEuid. Legacy values possible in historical deliveries only: no_typed_cpf, cpf_exempt, no_payer_data, and payer_company (equivalent to cnpj_mismatch)."},"payerEuid":{"type":"string","description":"Pseudonymous Eulen end-user id (EUID) of the bank account that actually paid, e.g. `EU015562310201680`. Present for every refund reason whenever known, except indeterminate_payer (during the closed 28/07/2026-01/08/2026 window the provider echoed the locked identity instead of the payer, so no payer field is trustworthy on those deliveries). Stable per payer: use it to correlate repeat refunded payers and block them at your checkout without ever seeing the payer's identity.","example":"EU015562310201680"},"payerName":{"type":"string","description":"Bank-account holder that actually paid. Present only when refundReasonDetail is cpf_mismatch or cnpj_mismatch."},"payerTaxNumber":{"type":"string","description":"Document of the paying account, always masked for CPFs (`***.999.999-**`, Eulen format — the full CPF is never sent); full 14-digit CNPJ for company/PSP accounts (public registry data). Present only when refundReasonDetail is cpf_mismatch or cnpj_mismatch.","example":"***.456.789-**"},"rejectionReasons":{"type":"array","items":{"type":"string"},"description":"Raw reason codes declared by the PIX provider, e.g. [\"PAYER_MISMATCH\"] (the paying account's document differs from the one the QR was locked to) or [\"BLOCKED_USER\"]. The same field payment.refund_pending carries. Live since 01/08/2026 — older refunds arrive without it. The vocabulary is the provider's and may grow, so treat unknown codes as generic risk refusals. Unlike refundReasonDetail this is the provider's own word, not our inference; it never widens the payer fields disclosed above.","example":["PAYER_MISMATCH"]},"eulenStatus":{"type":"string"},"refundedAt":{"type":"string","format":"date-time"},"credited":{"type":"boolean","description":"Present (always false) when the refund happened before any balance was credited; absent when a previously credited held amount was reversed."},"timestamp":{"type":"string","format":"date-time"},"purchaseId":{"type":"integer","description":"Present only for product checkout refunds."},"productId":{"type":"integer","description":"Present only for product checkout refunds."},"productTitle":{"type":"string","description":"Present only for product checkout refunds."},"buyerEmail":{"type":"string","format":"email","description":"Present only for product checkout refunds."},"buyerName":{"type":"string","nullable":true,"description":"Present only for product checkout refunds."}},"description":"Event-specific payload."}}}}}},"responses":{"2XX":{"description":"ACK — delivery counted as successful."},"4XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."},"5XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."}}}},"payment.refund_pending":{"post":{"summary":"Webhook: payment.refund_pending","description":"OrionPay delivers webhooks via HTTPS POST to the URL registered for the webhook — configured in the dashboard (/dashboard/api-webhooks) or via POST /api/v1/personal/products.\n\n**Headers** sent on every delivery:\n- `Content-Type: application/json`\n- `X-Webhook-Signature: <hex>` — lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No `sha256=` prefix. Absent when the webhook has no secret.\n\nThere are no `X-OrionPay-*` headers.\n\n**Body** — every event uses the same envelope; the event-specific fields documented per event live under `data`:\n```json\n{\n  \"event\": \"payment.success\",\n  \"productId\": 123,\n  \"productTitle\": \"Curso X\",\n  \"webhookName\": \"Default\",\n  \"timestamp\": \"2026-09-15T12:00:00.000Z\",\n  \"data\": { \"...\": \"event-specific fields\" }\n}\n```\n`productId` and `productTitle` are null for account-level (personal) webhooks.\n\n**Verification** (Node.js / Express) — compute the HMAC over the raw bytes received; re-serialized JSON is not guaranteed to match:\n```js\nconst crypto = require('crypto');\napp.post('/webhooks/orionpay', express.raw({ type: 'application/json' }), (req, res) => {\n  const received = Buffer.from(req.get('X-Webhook-Signature') || '', 'utf8');\n  const expected = Buffer.from(\n    crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'), 'utf8');\n  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {\n    return res.sendStatus(401);\n  }\n  const { event, data } = JSON.parse(req.body.toString('utf8'));\n  // handle event...\n  res.sendStatus(200);\n});\n```\n\n**Retries**: up to 8 attempts with exponential backoff starting at ~10s; each attempt times out after 10s. Only a 2xx response counts as delivered.\n**Idempotency**: the same event can be delivered more than once — deduplicate on `event` + `data.transactionId` (or `data.purchaseId` when there is no transaction id).","tags":["Webhooks"],"parameters":[{"name":"X-Webhook-Signature","in":"header","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No prefix. Absent when the webhook has no secret."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","productId","productTitle","webhookName","timestamp","data"],"properties":{"event":{"type":"string","const":"payment.refund_pending"},"productId":{"type":"integer","nullable":true,"description":"Product the webhook belongs to; null for account-level (personal) webhooks."},"productTitle":{"type":"string","nullable":true,"description":"Product title; null for account-level (personal) webhooks."},"webhookName":{"type":"string","description":"Name given to the webhook."},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 time the delivery was created."},"data":{"type":"object","description":"Advance notice: the PIX provider announced it will refund a payment it already received (risk/compliance). This is NOT the terminal event — payment.refunded follows, ~2h later on average, and up to 3 days when the provider holds the amount first. Historically irreversible: of 187 notices, 182 ended refunded and none ever settled. If the buyer had already been granted access (delay-depix sales grant on PIX confirmation, before settlement), OrionPay revokes it and reports accessRevoked: true — revoke your own side too if you fulfilled early. Event-specific payload.","properties":{"transactionId":{"type":"integer","description":"Dashboard test deliveries send a mock string id (`mock_<uuid>`) instead."},"amount":{"type":"number"},"reference":{"type":"string","nullable":true},"status":{"type":"string","enum":["refund_pending"]},"type":{"type":"string","example":"DEPOSIT"},"currency":{"type":"string","enum":["BRL"]},"reason":{"type":"string","enum":["eulen_will_refund"]},"rejectionReasons":{"type":"array","items":{"type":"string"},"description":"Raw reason codes declared by the PIX provider, e.g. [\"PAYER_MISMATCH\"] (the paying account's document differs from the one the QR was locked to) or [\"BLOCKED_USER\"]. Optional — older notices arrive without it. The vocabulary is the provider's and may grow, so treat unknown codes as generic risk refusals.","example":["PAYER_MISMATCH"]},"expectedRefundAt":{"type":"string","format":"date-time","description":"Optional. When the provider declared a hold before returning the money (typically 3 days), the date it expects to refund."},"payerEuid":{"type":"string","description":"Pseudonymous Eulen end-user id (EUID) of the account that paid, when known. Same semantics as in payment.refunded: stable per payer, safe to use for blocking repeat offenders at your checkout.","example":"EU015562310201680"},"eulenStatus":{"type":"string","enum":["will_refund"]},"credited":{"type":"boolean","description":"Always false — no balance was ever credited for this payment."},"accessRevoked":{"type":"boolean","description":"True when OrionPay revoked buyer access to the product because of this notice (the purchase moves to CANCELLED). False for account deposits and for purchases that had nothing to revoke."},"timestamp":{"type":"string","format":"date-time"},"purchaseId":{"type":"integer","description":"Present only for product checkout payments."},"productId":{"type":"integer","description":"Present only for product checkout payments."},"productTitle":{"type":"string","description":"Present only for product checkout payments."},"buyerEmail":{"type":"string","format":"email","description":"Present only for product checkout payments."},"buyerName":{"type":"string","nullable":true,"description":"Present only for product checkout payments."}}}}}}}},"responses":{"2XX":{"description":"ACK — delivery counted as successful."},"4XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."},"5XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."}}}},"med.created":{"post":{"summary":"Webhook: med.created","description":"OrionPay delivers webhooks via HTTPS POST to the URL registered for the webhook — configured in the dashboard (/dashboard/api-webhooks) or via POST /api/v1/personal/products.\n\n**Headers** sent on every delivery:\n- `Content-Type: application/json`\n- `X-Webhook-Signature: <hex>` — lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No `sha256=` prefix. Absent when the webhook has no secret.\n\nThere are no `X-OrionPay-*` headers.\n\n**Body** — every event uses the same envelope; the event-specific fields documented per event live under `data`:\n```json\n{\n  \"event\": \"payment.success\",\n  \"productId\": 123,\n  \"productTitle\": \"Curso X\",\n  \"webhookName\": \"Default\",\n  \"timestamp\": \"2026-09-15T12:00:00.000Z\",\n  \"data\": { \"...\": \"event-specific fields\" }\n}\n```\n`productId` and `productTitle` are null for account-level (personal) webhooks.\n\n**Verification** (Node.js / Express) — compute the HMAC over the raw bytes received; re-serialized JSON is not guaranteed to match:\n```js\nconst crypto = require('crypto');\napp.post('/webhooks/orionpay', express.raw({ type: 'application/json' }), (req, res) => {\n  const received = Buffer.from(req.get('X-Webhook-Signature') || '', 'utf8');\n  const expected = Buffer.from(\n    crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'), 'utf8');\n  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {\n    return res.sendStatus(401);\n  }\n  const { event, data } = JSON.parse(req.body.toString('utf8'));\n  // handle event...\n  res.sendStatus(200);\n});\n```\n\n**Retries**: up to 8 attempts with exponential backoff starting at ~10s; each attempt times out after 10s. Only a 2xx response counts as delivered.\n**Idempotency**: the same event can be delivered more than once — deduplicate on `event` + `data.transactionId` (or `data.purchaseId` when there is no transaction id).","tags":["Webhooks"],"parameters":[{"name":"X-Webhook-Signature","in":"header","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No prefix. Absent when the webhook has no secret."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","productId","productTitle","webhookName","timestamp","data"],"properties":{"event":{"type":"string","const":"med.created"},"productId":{"type":"integer","nullable":true,"description":"Product the webhook belongs to; null for account-level (personal) webhooks."},"productTitle":{"type":"string","nullable":true,"description":"Product title; null for account-level (personal) webhooks."},"webhookName":{"type":"string","description":"Name given to the webhook."},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 time the delivery was created."},"data":{"type":"object","properties":{"qrId":{"type":"string","description":"PIX charge id the MED was filed against."},"bankTxId":{"type":"string","nullable":true},"amount":{"type":"number","description":"BRL."},"currency":{"type":"string","enum":["BRL"]},"medDetectedAt":{"type":"string","format":"date-time"},"depositStatus":{"type":"string","description":"Provider status of the deposit when the MED was detected."},"payerTaxNumber":{"type":"string","nullable":true,"description":"Masked document of the paying account."},"transactionId":{"type":"integer"},"purchaseId":{"type":"integer","nullable":true},"productId":{"type":"integer","nullable":true},"productTitle":{"type":"string","nullable":true},"timestamp":{"type":"string","format":"date-time"}},"description":"Event-specific payload."}}}}}},"responses":{"2XX":{"description":"ACK — delivery counted as successful."},"4XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."},"5XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."}}}},"webhook.test":{"post":{"summary":"Webhook: webhook.test","description":"OrionPay delivers webhooks via HTTPS POST to the URL registered for the webhook — configured in the dashboard (/dashboard/api-webhooks) or via POST /api/v1/personal/products.\n\n**Headers** sent on every delivery:\n- `Content-Type: application/json`\n- `X-Webhook-Signature: <hex>` — lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No `sha256=` prefix. Absent when the webhook has no secret.\n\nThere are no `X-OrionPay-*` headers.\n\n**Body** — every event uses the same envelope; the event-specific fields documented per event live under `data`:\n```json\n{\n  \"event\": \"payment.success\",\n  \"productId\": 123,\n  \"productTitle\": \"Curso X\",\n  \"webhookName\": \"Default\",\n  \"timestamp\": \"2026-09-15T12:00:00.000Z\",\n  \"data\": { \"...\": \"event-specific fields\" }\n}\n```\n`productId` and `productTitle` are null for account-level (personal) webhooks.\n\n**Verification** (Node.js / Express) — compute the HMAC over the raw bytes received; re-serialized JSON is not guaranteed to match:\n```js\nconst crypto = require('crypto');\napp.post('/webhooks/orionpay', express.raw({ type: 'application/json' }), (req, res) => {\n  const received = Buffer.from(req.get('X-Webhook-Signature') || '', 'utf8');\n  const expected = Buffer.from(\n    crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'), 'utf8');\n  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {\n    return res.sendStatus(401);\n  }\n  const { event, data } = JSON.parse(req.body.toString('utf8'));\n  // handle event...\n  res.sendStatus(200);\n});\n```\n\n**Retries**: up to 8 attempts with exponential backoff starting at ~10s; each attempt times out after 10s. Only a 2xx response counts as delivered.\n**Idempotency**: the same event can be delivered more than once — deduplicate on `event` + `data.transactionId` (or `data.purchaseId` when there is no transaction id).","tags":["Webhooks"],"parameters":[{"name":"X-Webhook-Signature","in":"header","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No prefix. Absent when the webhook has no secret."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","productId","productTitle","webhookName","timestamp","data"],"properties":{"event":{"type":"string","const":"webhook.test"},"productId":{"type":"integer","nullable":true,"description":"Product the webhook belongs to; null for account-level (personal) webhooks."},"productTitle":{"type":"string","nullable":true,"description":"Product title; null for account-level (personal) webhooks."},"webhookName":{"type":"string","description":"Name given to the webhook."},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 time the delivery was created."},"data":{"type":"object","properties":{"message":{"type":"string","example":"Este é um teste de webhook"},"testId":{"type":"string"}},"description":"Event-specific payload."}}}}}},"responses":{"2XX":{"description":"ACK — delivery counted as successful."},"4XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."},"5XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."}}}},"withdrawal.completed":{"post":{"summary":"Webhook: withdrawal.completed","description":"OrionPay delivers webhooks via HTTPS POST to the URL registered for the webhook — configured in the dashboard (/dashboard/api-webhooks) or via POST /api/v1/personal/products.\n\n**Headers** sent on every delivery:\n- `Content-Type: application/json`\n- `X-Webhook-Signature: <hex>` — lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No `sha256=` prefix. Absent when the webhook has no secret.\n\nThere are no `X-OrionPay-*` headers.\n\n**Body** — every event uses the same envelope; the event-specific fields documented per event live under `data`:\n```json\n{\n  \"event\": \"payment.success\",\n  \"productId\": 123,\n  \"productTitle\": \"Curso X\",\n  \"webhookName\": \"Default\",\n  \"timestamp\": \"2026-09-15T12:00:00.000Z\",\n  \"data\": { \"...\": \"event-specific fields\" }\n}\n```\n`productId` and `productTitle` are null for account-level (personal) webhooks.\n\n**Verification** (Node.js / Express) — compute the HMAC over the raw bytes received; re-serialized JSON is not guaranteed to match:\n```js\nconst crypto = require('crypto');\napp.post('/webhooks/orionpay', express.raw({ type: 'application/json' }), (req, res) => {\n  const received = Buffer.from(req.get('X-Webhook-Signature') || '', 'utf8');\n  const expected = Buffer.from(\n    crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'), 'utf8');\n  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {\n    return res.sendStatus(401);\n  }\n  const { event, data } = JSON.parse(req.body.toString('utf8'));\n  // handle event...\n  res.sendStatus(200);\n});\n```\n\n**Retries**: up to 8 attempts with exponential backoff starting at ~10s; each attempt times out after 10s. Only a 2xx response counts as delivered.\n**Idempotency**: the same event can be delivered more than once — deduplicate on `event` + `data.transactionId` (or `data.purchaseId` when there is no transaction id).","tags":["Webhooks"],"parameters":[{"name":"X-Webhook-Signature","in":"header","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No prefix. Absent when the webhook has no secret."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","productId","productTitle","webhookName","timestamp","data"],"properties":{"event":{"type":"string","const":"withdrawal.completed"},"productId":{"type":"integer","nullable":true,"description":"Product the webhook belongs to; null for account-level (personal) webhooks."},"productTitle":{"type":"string","nullable":true,"description":"Product title; null for account-level (personal) webhooks."},"webhookName":{"type":"string","description":"Name given to the webhook."},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 time the delivery was created."},"data":{"type":"object","properties":{"withdrawalId":{"type":"integer"},"amount":{"type":"string","description":"Decimal string in the asset unit."},"fee":{"type":"string","description":"Decimal string."},"totalDeducted":{"type":"string","description":"Decimal string."},"asset":{"type":"string","example":"L-BTC","description":"BRL for PIX withdrawals."},"network":{"type":"string","example":"liquid","description":"PIX for PIX withdrawals."},"toAddress":{"type":"string","description":"Crypto withdrawals only."},"pixKey":{"type":"string","nullable":true,"description":"PIX withdrawals only."},"txHash":{"type":"string","nullable":true},"isSideShift":{"type":"boolean","description":"Crypto withdrawals only."},"completedAt":{"type":"string","format":"date-time"}},"description":"Event-specific payload."}}}}}},"responses":{"2XX":{"description":"ACK — delivery counted as successful."},"4XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."},"5XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."}}}},"withdrawal.settled":{"post":{"summary":"Webhook: withdrawal.settled","description":"OrionPay delivers webhooks via HTTPS POST to the URL registered for the webhook — configured in the dashboard (/dashboard/api-webhooks) or via POST /api/v1/personal/products.\n\n**Headers** sent on every delivery:\n- `Content-Type: application/json`\n- `X-Webhook-Signature: <hex>` — lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No `sha256=` prefix. Absent when the webhook has no secret.\n\nThere are no `X-OrionPay-*` headers.\n\n**Body** — every event uses the same envelope; the event-specific fields documented per event live under `data`:\n```json\n{\n  \"event\": \"payment.success\",\n  \"productId\": 123,\n  \"productTitle\": \"Curso X\",\n  \"webhookName\": \"Default\",\n  \"timestamp\": \"2026-09-15T12:00:00.000Z\",\n  \"data\": { \"...\": \"event-specific fields\" }\n}\n```\n`productId` and `productTitle` are null for account-level (personal) webhooks.\n\n**Verification** (Node.js / Express) — compute the HMAC over the raw bytes received; re-serialized JSON is not guaranteed to match:\n```js\nconst crypto = require('crypto');\napp.post('/webhooks/orionpay', express.raw({ type: 'application/json' }), (req, res) => {\n  const received = Buffer.from(req.get('X-Webhook-Signature') || '', 'utf8');\n  const expected = Buffer.from(\n    crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'), 'utf8');\n  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {\n    return res.sendStatus(401);\n  }\n  const { event, data } = JSON.parse(req.body.toString('utf8'));\n  // handle event...\n  res.sendStatus(200);\n});\n```\n\n**Retries**: up to 8 attempts with exponential backoff starting at ~10s; each attempt times out after 10s. Only a 2xx response counts as delivered.\n**Idempotency**: the same event can be delivered more than once — deduplicate on `event` + `data.transactionId` (or `data.purchaseId` when there is no transaction id).","tags":["Webhooks"],"parameters":[{"name":"X-Webhook-Signature","in":"header","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No prefix. Absent when the webhook has no secret."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","productId","productTitle","webhookName","timestamp","data"],"properties":{"event":{"type":"string","const":"withdrawal.settled"},"productId":{"type":"integer","nullable":true,"description":"Product the webhook belongs to; null for account-level (personal) webhooks."},"productTitle":{"type":"string","nullable":true,"description":"Product title; null for account-level (personal) webhooks."},"webhookName":{"type":"string","description":"Name given to the webhook."},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 time the delivery was created."},"data":{"type":"object","properties":{"withdrawalId":{"type":"integer","nullable":true},"amount":{"type":"string","description":"Decimal string in the asset unit."},"asset":{"type":"string"},"toAddress":{"type":"string"},"liquidTxHash":{"type":"string","nullable":true},"settleTxHash":{"type":"string","nullable":true},"settleNetwork":{"type":"string","nullable":true,"example":"bitcoin"},"completedAt":{"type":"string","format":"date-time"}},"description":"Event-specific payload."}}}}}},"responses":{"2XX":{"description":"ACK — delivery counted as successful."},"4XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."},"5XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."}}}},"withdrawal.failed":{"post":{"summary":"Webhook: withdrawal.failed","description":"OrionPay delivers webhooks via HTTPS POST to the URL registered for the webhook — configured in the dashboard (/dashboard/api-webhooks) or via POST /api/v1/personal/products.\n\n**Headers** sent on every delivery:\n- `Content-Type: application/json`\n- `X-Webhook-Signature: <hex>` — lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No `sha256=` prefix. Absent when the webhook has no secret.\n\nThere are no `X-OrionPay-*` headers.\n\n**Body** — every event uses the same envelope; the event-specific fields documented per event live under `data`:\n```json\n{\n  \"event\": \"payment.success\",\n  \"productId\": 123,\n  \"productTitle\": \"Curso X\",\n  \"webhookName\": \"Default\",\n  \"timestamp\": \"2026-09-15T12:00:00.000Z\",\n  \"data\": { \"...\": \"event-specific fields\" }\n}\n```\n`productId` and `productTitle` are null for account-level (personal) webhooks.\n\n**Verification** (Node.js / Express) — compute the HMAC over the raw bytes received; re-serialized JSON is not guaranteed to match:\n```js\nconst crypto = require('crypto');\napp.post('/webhooks/orionpay', express.raw({ type: 'application/json' }), (req, res) => {\n  const received = Buffer.from(req.get('X-Webhook-Signature') || '', 'utf8');\n  const expected = Buffer.from(\n    crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'), 'utf8');\n  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {\n    return res.sendStatus(401);\n  }\n  const { event, data } = JSON.parse(req.body.toString('utf8'));\n  // handle event...\n  res.sendStatus(200);\n});\n```\n\n**Retries**: up to 8 attempts with exponential backoff starting at ~10s; each attempt times out after 10s. Only a 2xx response counts as delivered.\n**Idempotency**: the same event can be delivered more than once — deduplicate on `event` + `data.transactionId` (or `data.purchaseId` when there is no transaction id).","tags":["Webhooks"],"parameters":[{"name":"X-Webhook-Signature","in":"header","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No prefix. Absent when the webhook has no secret."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","productId","productTitle","webhookName","timestamp","data"],"properties":{"event":{"type":"string","const":"withdrawal.failed"},"productId":{"type":"integer","nullable":true,"description":"Product the webhook belongs to; null for account-level (personal) webhooks."},"productTitle":{"type":"string","nullable":true,"description":"Product title; null for account-level (personal) webhooks."},"webhookName":{"type":"string","description":"Name given to the webhook."},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 time the delivery was created."},"data":{"type":"object","properties":{"withdrawalId":{"type":"integer","nullable":true},"amount":{"type":"string","description":"Decimal string."},"asset":{"type":"string","description":"Crypto withdrawals only."},"network":{"type":"string","description":"Crypto withdrawals only."},"toAddress":{"type":"string","description":"Crypto withdrawals only."},"method":{"type":"string","example":"PIX","description":"PIX withdrawals only."},"pixKey":{"type":"string","description":"PIX withdrawals only."},"error":{"type":"string"},"autoRefunded":{"type":"boolean","description":"Present (true) when the amount was returned to the balance automatically."},"failedAt":{"type":"string","format":"date-time"}},"description":"Event-specific payload."}}}}}},"responses":{"2XX":{"description":"ACK — delivery counted as successful."},"4XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."},"5XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."}}}},"swap.completed":{"post":{"summary":"Webhook: swap.completed","description":"OrionPay delivers webhooks via HTTPS POST to the URL registered for the webhook — configured in the dashboard (/dashboard/api-webhooks) or via POST /api/v1/personal/products.\n\n**Headers** sent on every delivery:\n- `Content-Type: application/json`\n- `X-Webhook-Signature: <hex>` — lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No `sha256=` prefix. Absent when the webhook has no secret.\n\nThere are no `X-OrionPay-*` headers.\n\n**Body** — every event uses the same envelope; the event-specific fields documented per event live under `data`:\n```json\n{\n  \"event\": \"payment.success\",\n  \"productId\": 123,\n  \"productTitle\": \"Curso X\",\n  \"webhookName\": \"Default\",\n  \"timestamp\": \"2026-09-15T12:00:00.000Z\",\n  \"data\": { \"...\": \"event-specific fields\" }\n}\n```\n`productId` and `productTitle` are null for account-level (personal) webhooks.\n\n**Verification** (Node.js / Express) — compute the HMAC over the raw bytes received; re-serialized JSON is not guaranteed to match:\n```js\nconst crypto = require('crypto');\napp.post('/webhooks/orionpay', express.raw({ type: 'application/json' }), (req, res) => {\n  const received = Buffer.from(req.get('X-Webhook-Signature') || '', 'utf8');\n  const expected = Buffer.from(\n    crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'), 'utf8');\n  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {\n    return res.sendStatus(401);\n  }\n  const { event, data } = JSON.parse(req.body.toString('utf8'));\n  // handle event...\n  res.sendStatus(200);\n});\n```\n\n**Retries**: up to 8 attempts with exponential backoff starting at ~10s; each attempt times out after 10s. Only a 2xx response counts as delivered.\n**Idempotency**: the same event can be delivered more than once — deduplicate on `event` + `data.transactionId` (or `data.purchaseId` when there is no transaction id).","tags":["Webhooks"],"parameters":[{"name":"X-Webhook-Signature","in":"header","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No prefix. Absent when the webhook has no secret."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","productId","productTitle","webhookName","timestamp","data"],"properties":{"event":{"type":"string","const":"swap.completed"},"productId":{"type":"integer","nullable":true,"description":"Product the webhook belongs to; null for account-level (personal) webhooks."},"productTitle":{"type":"string","nullable":true,"description":"Product title; null for account-level (personal) webhooks."},"webhookName":{"type":"string","description":"Name given to the webhook."},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 time the delivery was created."},"data":{"type":"object","properties":{"txid":{"type":"string","nullable":true},"fromCurrency":{"type":"string"},"toCurrency":{"type":"string"},"fromAmount":{"type":"string","description":"Decimal string."},"toAmountGross":{"type":"string","description":"Decimal string, before the conversion fee."},"toAmountNet":{"type":"string","description":"Decimal string, credited amount."},"feePercent":{"type":"string"},"feeAmount":{"type":"string"},"completedAt":{"type":"string","format":"date-time"}},"description":"Event-specific payload."}}}}}},"responses":{"2XX":{"description":"ACK — delivery counted as successful."},"4XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."},"5XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."}}}},"swap.failed":{"post":{"summary":"Webhook: swap.failed","description":"OrionPay delivers webhooks via HTTPS POST to the URL registered for the webhook — configured in the dashboard (/dashboard/api-webhooks) or via POST /api/v1/personal/products.\n\n**Headers** sent on every delivery:\n- `Content-Type: application/json`\n- `X-Webhook-Signature: <hex>` — lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No `sha256=` prefix. Absent when the webhook has no secret.\n\nThere are no `X-OrionPay-*` headers.\n\n**Body** — every event uses the same envelope; the event-specific fields documented per event live under `data`:\n```json\n{\n  \"event\": \"payment.success\",\n  \"productId\": 123,\n  \"productTitle\": \"Curso X\",\n  \"webhookName\": \"Default\",\n  \"timestamp\": \"2026-09-15T12:00:00.000Z\",\n  \"data\": { \"...\": \"event-specific fields\" }\n}\n```\n`productId` and `productTitle` are null for account-level (personal) webhooks.\n\n**Verification** (Node.js / Express) — compute the HMAC over the raw bytes received; re-serialized JSON is not guaranteed to match:\n```js\nconst crypto = require('crypto');\napp.post('/webhooks/orionpay', express.raw({ type: 'application/json' }), (req, res) => {\n  const received = Buffer.from(req.get('X-Webhook-Signature') || '', 'utf8');\n  const expected = Buffer.from(\n    crypto.createHmac('sha256', WEBHOOK_SECRET).update(req.body).digest('hex'), 'utf8');\n  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {\n    return res.sendStatus(401);\n  }\n  const { event, data } = JSON.parse(req.body.toString('utf8'));\n  // handle event...\n  res.sendStatus(200);\n});\n```\n\n**Retries**: up to 8 attempts with exponential backoff starting at ~10s; each attempt times out after 10s. Only a 2xx response counts as delivered.\n**Idempotency**: the same event can be delivered more than once — deduplicate on `event` + `data.transactionId` (or `data.purchaseId` when there is no transaction id).","tags":["Webhooks"],"parameters":[{"name":"X-Webhook-Signature","in":"header","required":false,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"},"description":"Lowercase hex HMAC-SHA256 of the raw request body, keyed with the webhook secret. No prefix. Absent when the webhook has no secret."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","productId","productTitle","webhookName","timestamp","data"],"properties":{"event":{"type":"string","const":"swap.failed"},"productId":{"type":"integer","nullable":true,"description":"Product the webhook belongs to; null for account-level (personal) webhooks."},"productTitle":{"type":"string","nullable":true,"description":"Product title; null for account-level (personal) webhooks."},"webhookName":{"type":"string","description":"Name given to the webhook."},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 time the delivery was created."},"data":{"type":"object","description":"Two shapes: a failure ({fromCurrency, toCurrency, fromAmount, error, step, txid?, failedAt}) or a refund of the blocked amount ({transactionId, fromCurrency, fromAmount, step: REFUNDED, reason, autoRefunded?, refundedAt}). Event-specific payload.","properties":{"transactionId":{"type":"integer","description":"Refunds only."},"fromCurrency":{"type":"string"},"toCurrency":{"type":"string","description":"Failures only."},"fromAmount":{"oneOf":[{"type":"string"},{"type":"number"}],"description":"Decimal string on failures; number on refunds."},"step":{"type":"string","example":"SWAP_FAILED","description":"SWAP_FAILED, CREDIT_FAILED, ERROR or REFUNDED."},"error":{"type":"string","description":"Failures only."},"txid":{"type":"string","nullable":true,"description":"Present when the swap was broadcast before failing (CREDIT_FAILED)."},"reason":{"type":"string","description":"Refunds only."},"autoRefunded":{"type":"boolean","description":"Refunds only: present (true) on automatic refunds."},"failedAt":{"type":"string","format":"date-time","description":"Failures only."},"refundedAt":{"type":"string","format":"date-time","description":"Refunds only."}}}}}}}},"responses":{"2XX":{"description":"ACK — delivery counted as successful."},"4XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."},"5XX":{"description":"Treated as failure; OrionPay retries with exponential backoff (up to 8 attempts)."}}}}},"components":{"securitySchemes":{"ApiKey":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Product API key (associated with a product) or personal API key (productId=null). Generate at https://pay.orion.moe/dashboard/api-webhooks."},"BearerApiKey":{"type":"http","scheme":"bearer","description":"Same as X-API-Key but passed as `Authorization: Bearer <key>`."}},"schemas":{"ErrorResponse":{"type":"object","properties":{"statusCode":{"type":"integer","example":400},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"error":{"type":"string"},"code":{"type":"string","description":"Stable machine-readable error code, when the endpoint defines one. Match on this, not on the message text."}},"required":["statusCode","message"]},"GeneratePixRequest":{"type":"object","required":["name","email"],"description":"Payer identification is required since 2026-06-30 for amounts ≥ R$10.00 (provider enforcement): send cpf OR euid. Amounts strictly below R$10.00 are exempt since 2026-07-21. Requests with neither, when required, are rejected with 400 \"Informe o CPF/CNPJ ou o EUID do depositante.\"","properties":{"amount":{"type":"number","minimum":0.01,"description":"BRL amount. Optional when product has a fixed price."},"description":{"type":"string","description":"Optional description shown to payer."},"name":{"type":"string","description":"Payer full name."},"email":{"type":"string","format":"email","description":"Payer email."},"phone":{"type":"string","description":"Optional payer phone (E.164 or Brazilian)."},"cpf":{"type":"string","description":"Payer CPF (11 digits) or CNPJ (14 digits) — REQUIRED since 2026-06-30 for amounts ≥ R$10.00 unless euid is sent (amounts strictly below R$10.00 are exempt since 2026-07-21). Punctuation accepted; checksum-validated."},"euid":{"type":"string","maxLength":100,"description":"Payer DePix EUID — alternative to cpf as the payer identification."},"ipAddress":{"type":"string","description":"Optional payer IP for fraud scoring."}}},"PixResponse":{"type":"object","properties":{"qrCode":{"type":"string","description":"BR Code (PIX copy-paste string)."},"qrCodeImage":{"type":"string","description":"Base64-encoded QR code image (data URL)."},"transactionId":{"type":"integer"},"purchaseId":{"type":"integer","nullable":true},"eulenDepositId":{"type":"string","format":"uuid"},"amount":{"type":"number"},"expiresAt":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["PENDING","PAID","EXPIRED","FAILED"]}}},"PaymentStatusResponse":{"type":"object","properties":{"status":{"type":"string","enum":["PENDING","PAID","EXPIRED","FAILED"]},"paid":{"type":"boolean"},"amount":{"type":"number"},"paidAt":{"type":"string","format":"date-time","nullable":true},"buyerEmail":{"type":"string","format":"email","nullable":true},"transactionId":{"type":"integer","nullable":true},"purchaseId":{"type":"integer","nullable":true},"accessToken":{"type":"string","nullable":true,"description":"Access token granted to buyer on payment."}}},"VerifyAccessResponse":{"type":"object","properties":{"hasAccess":{"type":"boolean"},"purchaseId":{"type":"integer","nullable":true},"buyerEmail":{"type":"string","format":"email","nullable":true},"buyerName":{"type":"string","nullable":true},"grantedAt":{"type":"string","format":"date-time","nullable":true},"expiresAt":{"type":"string","format":"date-time","nullable":true}}},"Buyer":{"type":"object","properties":{"purchaseId":{"type":"integer"},"buyerEmail":{"type":"string","format":"email"},"buyerName":{"type":"string","nullable":true},"status":{"type":"string","enum":["PENDING","PAID","EXPIRED","CANCELED","REFUNDED"]},"price":{"type":"number"},"paidAt":{"type":"string","format":"date-time","nullable":true},"accessToken":{"type":"string","nullable":true},"expiresAt":{"type":"string","format":"date-time","nullable":true}}},"GrantAccessRequest":{"type":"object","required":["buyerEmail"],"properties":{"buyerEmail":{"type":"string","format":"email"},"buyerName":{"type":"string"},"price":{"type":"number","description":"Optional recorded price (no charge is made)."},"expiresAt":{"type":"string","format":"date-time","description":"Optional access expiry."}}},"CreatePixDepositRequest":{"type":"object","required":[],"description":"Payer identification is required since 2026-06-30 for amounts ≥ R$10.00 (provider enforcement): send cpf OR euid. Amounts strictly below R$10.00 are exempt since 2026-07-21. Requests with neither, when required, are rejected with 400 \"Informe o CPF/CNPJ ou o EUID do depositante.\"","properties":{"cpf":{"type":"string","pattern":"^\\d{11}(\\d{3})?$","description":"Payer CPF (11 digits) or CNPJ (14 digits), digits only — REQUIRED since 2026-06-30 for amounts ≥ R$10.00 unless euid is sent (amounts strictly below R$10.00 are exempt since 2026-07-21); checksum-validated. The merchant's identification (merchantId) is added automatically server-side; do not send it."},"euid":{"type":"string","maxLength":100,"description":"Payer DePix EUID — alternative to cpf as the payer identification."},"buyerName":{"type":"string"},"buyerEmail":{"type":"string","format":"email"},"amount":{"type":"number","minimum":0.01}}},"BalanceResponse":{"type":"object","properties":{"brl":{"type":"number","description":"BRL wallet balance."},"brlBlocked":{"type":"number","description":"BRL pending in withdrawals/swaps."},"crypto":{"type":"array","items":{"type":"object","properties":{"asset":{"type":"string","example":"L-BTC"},"network":{"type":"string","example":"liquid"},"balance":{"type":"string","description":"Balance as decimal string for precision."},"address":{"type":"string","nullable":true}}}}}},"WithdrawLimitsResponse":{"type":"object","properties":{"daily":{"type":"object","properties":{"limit":{"type":"number"},"used":{"type":"number"}}},"monthly":{"type":"object","properties":{"limit":{"type":"number"},"used":{"type":"number"}}},"perTransaction":{"type":"object","properties":{"min":{"type":"number"},"max":{"type":"number"}}}}},"TransactionListResponse":{"type":"object","properties":{"total":{"type":"integer"},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"type":{"type":"string","enum":["DEPOSIT","WITHDRAWAL","SWAP"]},"status":{"type":"string"},"amount":{"type":"number"},"currency":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"metadata":{"type":"object","additionalProperties":true}}}}}},"PixWithdrawRequest":{"type":"object","required":["pixKey"],"description":"Send exactly one of `amount` or `netAmount`. Sending both, or neither, is rejected with 400. Fees are included in the debit: use GET /api/v1/personal/withdraw/quote to preview the breakdown.","properties":{"amount":{"type":"number","minimum":1,"maximum":100000,"description":"Gross BRL amount debited from the wallet; fees come out of it. Mutually exclusive with netAmount."},"netAmount":{"type":"number","minimum":1,"maximum":100000,"description":"BRL amount that must reach the recipient; the gross debit (net + fees) is derived automatically and must not exceed R$ 100000.00. Mutually exclusive with amount."},"pixKey":{"type":"string","description":"PIX key (CPF/CNPJ/email/phone/random)."},"pixKeyType":{"type":"string","enum":["CPF","CNPJ","EMAIL","PHONE","EVP"],"description":"Optional; auto-detected if omitted."},"description":{"type":"string"},"taxNumber":{"type":"string","pattern":"^\\d{11}$|^\\d{14}$","description":"Tax number of the recipient (CPF/CNPJ)."},"euid":{"type":"string","description":"Optional Eulen user identifier override."}}},"CryptoWithdrawRequest":{"type":"object","required":["amount","toAddress","assetTicker"],"properties":{"amount":{"type":"number","minimum":0,"description":"Amount in the asset's native unit."},"toAddress":{"type":"string","description":"Destination wallet address."},"assetTicker":{"type":"string","enum":["L-BTC","BTC-PEGOUT","DePix","USDt","USDC"],"description":"BTC-PEGOUT: peg-out to Bitcoin mainnet (min 0.00025 BTC)."},"network":{"type":"string","enum":["liquid","bitcoin","ethereum","tron","bsc","solana","ton","avalanche","polygon","optimism"],"description":"Optional network override (e.g. for SideShift cross-chain routing)."},"memo":{"type":"string","description":"Required for TON."}}},"PixWithdrawQuoteResponse":{"type":"object","description":"Computed with the same math as POST /api/v1/personal/withdraw/pix, so the quote matches what is charged.","properties":{"success":{"type":"boolean","const":true},"quote":{"type":"object","properties":{"grossAmount":{"type":"number","description":"BRL debited from the wallet."},"totalFee":{"type":"number","description":"Total BRL fee included in the debit."},"netAmount":{"type":"number","description":"BRL the recipient receives."},"requestedNetAmount":{"type":"number","description":"Present only when the quote was requested with netAmount."}}},"limits":{"type":"object","properties":{"minAmount":{"type":"number","description":"Minimum gross debit per withdrawal."},"maxAmount":{"type":"number","description":"Maximum gross debit per withdrawal for this account."}}},"validation":{"type":"object","properties":{"withinLimits":{"type":"boolean"},"reason":{"type":"string","description":"Present only when withinLimits is false (Portuguese text)."}}}}},"WithdrawalQueuedResponse":{"type":"object","properties":{"transactionId":{"type":"integer"},"status":{"type":"string","enum":["QUEUED","PENDING","PROCESSING"]},"amount":{"type":"number"},"asset":{"type":"string"},"estimatedSettlementAt":{"type":"string","format":"date-time","nullable":true}}},"WithdrawalStatusResponse":{"type":"object","properties":{"transactionId":{"type":"integer"},"status":{"type":"string","enum":["QUEUED","PROCESSING","COMPLETED","SETTLED","FAILED","REJECTED"]},"amount":{"type":"number"},"asset":{"type":"string"},"txHash":{"type":"string","nullable":true},"failureReason":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"settledAt":{"type":"string","format":"date-time","nullable":true}}},"SwapRequest":{"type":"object","required":["fromCurrency","toCurrency","amount"],"properties":{"fromCurrency":{"type":"string","enum":["DePix","L-BTC","USDt","EURx"]},"toCurrency":{"type":"string","enum":["DePix","L-BTC","USDt","EURx"]},"amount":{"type":"number","minimum":0}}},"SwapQuoteResponse":{"type":"object","properties":{"quoteId":{"type":"string"},"fromCurrency":{"type":"string"},"toCurrency":{"type":"string"},"fromAmount":{"type":"number"},"toAmount":{"type":"number"},"rate":{"type":"number"},"fee":{"type":"number"},"expiresAt":{"type":"string","format":"date-time"}}},"SwapExecuteResponse":{"type":"object","properties":{"transactionId":{"type":"integer"},"status":{"type":"string","enum":["QUEUED","PROCESSING","COMPLETED","FAILED"]},"fromAmount":{"type":"number"},"toAmount":{"type":"number"},"fromCurrency":{"type":"string"},"toCurrency":{"type":"string"}}},"WithdrawEnabledResponse":{"type":"object","properties":{"enabled":{"type":"boolean"},"depositHoldDays":{"type":"integer","description":"7-day hold applies to all deposits while API withdrawals are enabled."},"enabledAt":{"type":"string","format":"date-time","nullable":true}}},"EnableWithdrawalsRequest":{"type":"object","required":["acceptTerms"],"properties":{"acceptTerms":{"type":"boolean","description":"Must be true; acknowledges the 7-day hold on all deposits while API withdrawals are enabled."}}},"CreateProductRequest":{"type":"object","additionalProperties":false,"required":["title","productType"],"description":"Any key not listed here, at any level (e.g. metadata, thumbnailUrl), is rejected with 400 INVALID_FIELD naming the field.","properties":{"title":{"type":"string","minLength":1,"maxLength":191,"description":"Product title (trimmed; at most 191 characters after trimming)."},"productType":{"type":"string","enum":["CURSO_ONLINE","FOTOS","ARQUIVOS","AREA_MEMBROS","TELEGRAM_CHANNEL","CONSULTORIA","MENTORIA"],"description":"Digital product types only. RIFA, PRODUTO_FISICO and FUNDING are dashboard-only."},"description":{"type":"string","maxLength":5000},"price":{"type":"number","minimum":0,"maximum":9999999999999.99,"description":"BRL, at most 2 decimal places. 0 or absent = open price: each pix/generate call sets `amount`, and no checkout link is created. A price above 0 must reach the platform minimum PIX order (400 MIN_PIX_ORDER_AMOUNT)."},"hideFromMarketplace":{"type":"boolean","default":true,"description":"Keep the product out of the public marketplace listing."},"publish":{"type":"boolean","default":true,"description":"Publish the product (status PUBLISHED, creates the checkout link). pix/generate requires a PUBLISHED product; false leaves it DRAFT."},"externalReference":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[A-Za-z0-9._:-]+$","description":"Your own id for the product, unique per account and case-sensitive. Repeating a call with the same value returns the stored product (200, replayed: true) instead of creating a duplicate. Not a secret."},"delivery":{"type":"object","description":"Only for AREA_MEMBROS ({accessUrl, accessInstructions}) and TELEGRAM_CHANNEL ({channelLink, joinInstructions}); sent with any other productType it is rejected with 400. Shown to the buyer on the /acesso page.","oneOf":[{"title":"AREA_MEMBROS delivery","type":"object","additionalProperties":false,"required":["accessUrl"],"properties":{"accessUrl":{"type":"string","format":"uri","maxLength":2048,"description":"https URL of the members area."},"accessInstructions":{"type":"string","maxLength":5000}}},{"title":"TELEGRAM_CHANNEL delivery","type":"object","additionalProperties":false,"required":["channelLink"],"properties":{"channelLink":{"type":"string","format":"uri","maxLength":2048,"description":"https invite link of the channel."},"joinInstructions":{"type":"string","maxLength":5000}}}]},"webhook":{"type":"object","additionalProperties":false,"required":["url"],"description":"Optional webhook created for the new product. Deliveries are signed with X-Webhook-Signature.","properties":{"url":{"type":"string","format":"uri","description":"Public https URL with a domain name (IP addresses, localhost and private hosts are rejected)."},"events":{"type":"array","minItems":1,"items":{"type":"string","enum":["purchase.created","payment.received","payment.success","access.granted","payment.under_review","payment.refund_pending","payment.refunded","payment.refunded_med","med.created","*"]},"default":["purchase.created","access.granted","payment.success"],"description":"Product events to subscribe to, or [\"*\"] for all."},"name":{"type":"string","minLength":1,"maxLength":255,"default":"Default"},"secret":{"type":"string","minLength":32,"maxLength":128,"description":"HMAC secret. When omitted, a random 64-character hex secret is generated and returned once in the response."}}}}},"CreateProductResponse":{"type":"object","required":["success","replayed","product","paymentLink","apiKey","webhook"],"properties":{"success":{"type":"boolean","const":true},"replayed":{"type":"boolean","description":"false on creation (201); true when an existing product with the same externalReference was returned (200)."},"product":{"type":"object","properties":{"id":{"type":"integer","example":123},"title":{"type":"string","example":"Curso X"},"description":{"type":"string","nullable":true},"productType":{"type":"string","enum":["CURSO_ONLINE","FOTOS","ARQUIVOS","AREA_MEMBROS","TELEGRAM_CHANNEL","CONSULTORIA","MENTORIA"]},"price":{"type":"number","nullable":true,"example":49.9,"description":"BRL as a number; null/0 means open price."},"status":{"type":"string","enum":["DRAFT","PUBLISHED","ARCHIVED"]},"hideFromMarketplace":{"type":"boolean"},"externalReference":{"type":"string","nullable":true,"example":"sku-123"},"createdAt":{"type":"string","format":"date-time"}}},"paymentLink":{"type":"object","nullable":true,"description":"Hosted checkout. null when price is 0/absent or the product is not PUBLISHED.","properties":{"slug":{"type":"string"},"url":{"type":"string","format":"uri","example":"https://pay.orion.moe/p/3f9c…"}}},"apiKey":{"type":"object","nullable":true,"description":"Product API key: use it for pix/generate, pix/status and the integration endpoints. Store it as a secret. apiKey is null on replay when the product key was disabled or expired in the dashboard — manage it there (a replay never creates a second key).","properties":{"id":{"type":"integer","example":456},"key":{"type":"string","example":"opay_<64 hex>","description":"Full key value, sent as X-API-Key."},"name":{"type":"string","example":"Curso X"},"createdAt":{"type":"string","format":"date-time"}}},"webhook":{"type":"object","nullable":true,"description":"null when no webhook was requested and none exists for the product.","properties":{"id":{"type":"integer","example":789},"name":{"type":"string","example":"Default"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["purchase.created","payment.received","payment.success","access.granted","payment.under_review","payment.refund_pending","payment.refunded","payment.refunded_med","med.created","*"]}},"isActive":{"type":"boolean"},"secret":{"type":"string","nullable":true,"description":"HMAC secret used for X-Webhook-Signature. Store it as a secret."}}}}},"CreateProductErrorResponse":{"type":"object","required":["statusCode","message","code"],"properties":{"statusCode":{"type":"integer","example":400},"message":{"type":"string","description":"Portuguese text starting with \"API:\" and ending with the code in parentheses, e.g. `API: campo \"price\" inválido: use no máximo 2 casas decimais (INVALID_FIELD)`."},"code":{"type":"string","enum":["PERSONAL_KEY_REQUIRED","PERSONAL_API_DISABLED","OFFICIAL_STORE_ACCOUNT","BUSINESS_NOT_ACTIVATED","BUSINESS_EXPIRED","INVALID_FIELD","MIN_PIX_ORDER_AMOUNT","PRODUCT_REFERENCE_EXISTS","API_PRODUCT_DAILY_LIMIT","PROVISIONING_IN_PROGRESS"]},"field":{"type":"string","description":"INVALID_FIELD only: dotted path of the offending field, e.g. `webhook.url`."},"productId":{"type":"integer","description":"PRODUCT_REFERENCE_EXISTS only: id of the product that holds the externalReference."}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"RateLimited":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"BadRequest":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Forbidden":{"description":"Key type, account state or feature switch does not allow this call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"x-mcp":{"transport":"streamable-http","url":"https://payapi.orion.moe/mcp","auth":{"type":"header","name":"X-API-Key"},"tools":["pix_generate","pix_status","integration_verify_access","integration_list_buyers","integration_get_buyer","integration_get_content","integration_grant_access","integration_revoke_access","integration_deposit_pix","personal_create_product","personal_balance","personal_limits","personal_transactions","personal_withdraw_pix","personal_withdraw_crypto","personal_withdraw_status","personal_swap_quote","personal_swap_execute","personal_withdraw_enabled"]}}