# PaySync > Gateway de pagamentos brasileiro para vendas online com PIX instantaneo, cartao de credito, criptomoedas e integracao Discord. Comece gratis. ## Sobre PaySync e uma plataforma de pagamentos digital para lojas online, comunidades Discord e desenvolvedores. Aceita PIX instantaneo, cartao de credito/debito (Visa, Mastercard, Elo, Amex), boleto bancario e criptomoedas (LTC). Oferece checkout de alta conversao, bot de vendas para Discord, sistema de afiliados e API RESTful completa. - Empresa: PAYSYNC PAGAMENTOS LTDA - CNPJ: 65.195.121/0001-81 - Site: [https://usepaysync.com](https://usepaysync.com) - API: [https://api.usepaysync.com](https://api.usepaysync.com) - Documentacao: [https://usepaysync.com/documentacao](https://usepaysync.com/documentacao) - Termos: [https://usepaysync.com/termos](https://usepaysync.com/termos) - Privacidade: [https://usepaysync.com/privacidade](https://usepaysync.com/privacidade) - Contato: contato@usepaysync.com ## Para quem e? - Lojas online e e-commerces - Criadores de conteudo com comunidades Discord - SaaS e plataformas de assinatura - Infoprodutores e vendedores digitais ## Precos - Sem mensalidade ou taxa de setup - Pague apenas por transacao aprovada - PIX: a partir de 2% + R$0,50 - Liquidacao PIX: instantanea | Cartao: D+7 ## Documentacao completa da API - [llms-full.txt](https://usepaysync.com/llms-full.txt) --- # PaySync API > Documentation for AI agents and LLMs integrating with PaySync payment platform. > Base URL: https://api.usepaysync.com > Version: v1 > Last updated: 2026-03-18 ## Authentication All API requests require a Bearer token in the Authorization header. Key prefixes: - `ps_live_` → Production environment - `ps_test_` → Sandbox environment (simulated data, no real charges) Header format: ``` Authorization: Bearer ps_live_ ``` Rate limit: 120 requests/minute per key. --- ## Error Format All errors return JSON: ```json { "error": "Human-readable error message" } ``` | Code | Meaning | |------|---------| | 400 | Invalid or missing parameter | | 401 | Invalid, missing, or revoked API key | | 404 | Resource not found | | 429 | Rate limit exceeded | | 500 | Internal server error | | 502 | Payment gateway failure | --- ## Products ### POST /v1/products Create a product to reference in payments and subscriptions. Request body (JSON): | Field | Type | Required | Rules | |-------------|--------|----------|--------------------------------| | name | string | Yes | max 200 chars | | description | string | No | max 500 chars | | priceCents | number | Yes | integer >= 100 (R$ 1.00 min) | | currency | string | No | default "BRL", max 3 chars | | metadata | string | No | JSON string, max 2048 chars | Limit: 500 products per account per environment. Response (201): ```json { "_id": "60f7b2a1c3e4d5f6a7b8c9d0", "name": "Premium Plan", "description": "Monthly access", "priceCents": 4990, "currency": "BRL", "active": true, "metadata": "{}", "createdAt": "2026-03-18T10:00:00.000Z" } ``` ### GET /v1/products List all products. Query params: | Param | Type | Default | Description | |-----------------|--------|---------|--------------------------------| | includeInactive | string | — | Set to "true" to include inactive products | Response (200): ```json { "products": [ { "_id": "...", "name": "...", "priceCents": 4990, "currency": "BRL", "active": true, "createdAt": "..." } ] } ``` ### GET /v1/products/:id Get a single product by ID. Response (200): Single product object (same fields as list items). ### PUT /v1/products/:id Update a product. All fields optional. Request body (JSON): | Field | Type | Rules | |-------------|---------|-------------------------------| | name | string | cannot be empty, max 200 | | description | string | max 500 | | priceCents | number | >= 100 | | active | boolean | — | | metadata | string | max 2048 | Response (200): Updated product object. ### DELETE /v1/products/:id Delete a product. Response (200): ```json { "deleted": true } ``` --- ## Payments (PIX) ### POST /v1/payments Create a payment. Supports PIX (default) and LTC (Litecoin). Send either `productId` (price comes from product) or `valueCents`. Request body (JSON): | Field | Type | Required | Rules | |---------------------|--------|------------------|---------------------------------------| | productId | string | Yes (or valueCents) | ObjectId of an active product | | valueCents | number | Yes (or productId) | integer >= 80 | | paymentMethod | string | No | `"pix"` (default) or `"ltc"` | | description | string | No | max 200 chars, default "Pagamento" | | callbackUrl | string | No | HTTPS URL, max 500 chars | | customer.name | string | No | max 100 chars | | customer.email | string | No | max 255 chars | | customer.externalId | string | No | max 200 chars | | metadata | string | No | JSON string, max 2048 chars | If both `productId` and `valueCents` are sent, the product price takes precedence. LTC payments are not available in sandbox mode. Response PIX (200): ```json { "paymentId": "psa_a1b2c3d4e5f6...", "status": "pending", "type": "one_time", "paymentMethod": "pix", "amountCents": 4990, "currency": "BRL", "productName": "Premium Plan", "environment": "live", "pix": { "brCode": "00020126580014br.gov.bcb.pix0136...", "qrCodeImage": "https://api.openpix.com.br/..." }, "expiresAt": "2026-03-18T12:30:00.000Z" } ``` Response LTC (200): ```json { "paymentId": "psa_a1b2c3d4e5f6...", "status": "pending", "type": "one_time", "paymentMethod": "ltc", "amountCents": 4990, "currency": "BRL", "productName": "Premium Plan", "environment": "live", "ltc": { "address": "LNxziNbcLNFqW6hCv7yeAcQG2iLTdMyG86", "amount": 0.14054997, "amountBrl": 49.90, "ltcPriceBrl": 355.12 }, "expiresAt": "2026-03-18T12:55:00.000Z" } ``` ### GET /v1/payments/:paymentId Get payment details. Path param: `paymentId` (string, e.g. `psa_...`) Response (200): ```json { "paymentId": "psa_a1b2c3d4...", "status": "paid", "type": "one_time", "paymentMethod": "pix", "environment": "live", "amountCents": 4990, "currency": "BRL", "productName": "Premium Plan", "customer": { "name": "Joao Silva", "email": "joao@email.com", "externalId": "user_123" }, "metadata": "{}", "paidAt": "2026-03-18T12:05:00.000Z", "expiresAt": "2026-03-18T12:30:00.000Z", "createdAt": "2026-03-18T12:00:00.000Z" } ``` Status values: `pending`, `paid`, `expired`, `refunded` paymentMethod values: `pix`, `ltc` ### GET /v1/payments List payments with pagination and filtering. Query params: | Param | Type | Default | Rules | |--------|--------|---------|--------------------------------------| | limit | number | 50 | 1–100 | | offset | number | 0 | >= 0 | | status | string | — | pending, paid, expired, refunded | | type | string | — | one_time, subscription | Response (200): ```json { "payments": [ { "paymentId": "...", "status": "...", "amountCents": 4990, "paidAt": null, "createdAt": "..." } ], "total": 42, "limit": 50, "offset": 0 } ``` --- ## Charges (standalone PIX — no product required) ### POST /v1/charges Create a standalone PIX charge with a custom amount. Request body (JSON): | Field | Type | Required | Rules | |---------------------|--------|----------|---------------------------------------| | valueCents | number | Yes | integer >= 100 | | description | string | No | max 200 chars, default "Pagamento PIX"| | callbackUrl | string | No | HTTPS URL, max 500 chars | | customer.name | string | No | max 100 chars | | customer.email | string | No | max 255 chars | | customer.externalId | string | No | max 200 chars | | metadata | string | No | JSON string, max 2048 chars | Response (201): ```json { "paymentId": "psc_a1b2c3d4e5f6...", "status": "pending", "amountCents": 1200, "currency": "BRL", "environment": "live", "pix": { "brCode": "00020126580014br.gov.bcb.pix0136...", "qrCodeImage": "https://api.openpix.com.br/..." }, "expiresAt": "2026-03-18T12:30:00.000Z" } ``` ### GET /v1/charges/:paymentId Get charge details. Path param: `paymentId` (string, e.g. `psc_...`) Response (200): ```json { "paymentId": "psc_a1b2c3d4...", "status": "paid", "amountCents": 1200, "currency": "BRL", "description": "Pagamento PIX", "customer": { "name": "", "email": "", "externalId": "" }, "metadata": "{}", "paidAt": "2026-03-18T12:05:00.000Z", "expiresAt": "2026-03-18T12:30:00.000Z", "createdAt": "2026-03-18T12:00:00.000Z" } ``` --- ## Split Charges (PIX dividido entre N contas) Permite criar uma cobrança PIX que, ao ser paga, é automaticamente dividida entre múltiplos beneficiários (contas paysync já cadastradas). Útil para marketplaces, comissionamento e gestão de royalties. **Regras críticas de validação** (rejeição imediata com 400 se quebradas): - `splits` array: 2 a 10 beneficiários - Cada `percentage`: entre 0.01 e 99.99 (não aceita 0% nem 100% individual) - Soma das `percentage`: EXATAMENTE 100.00 (tolerância 0.01 pra ponto flutuante) - `recipientEmail` único por charge (não dá pra duplicar beneficiário) - Beneficiário deve ser uma conta paysync existente (validado por email) - Beneficiário NÃO pode ser o próprio dono da API key - `amountCents`: integer entre 80 (R$ 0.80) e 500.000 (R$ 5.000) — cap conservador **Fluxo de pagamento**: 1. Você cria a split charge → recebe `brCode` (PIX copia-e-cola). 2. Cliente paga o valor cheio. 3. Paysync deduz a taxa do gateway do total. 4. Valor líquido é dividido conforme as percentages. 5. Cada beneficiário recebe o seu na própria carteira paysync. 6. Último beneficiário recebe `netCents - soma(outros)` para garantir centavo exato. **LGPD**: emails dos beneficiários são mascarados em todas as respostas (GET/POST). Só armazenamos o ID interno + email pra audit. Webhook recebe o mesmo formato mascarado. ### POST /v1/split-charges Create a PIX charge that splits between recipients on payment. ```bash curl -X POST https://api.usepaysync.com/api/v1/split-charges \ -H "Authorization: Bearer ps_live_..." \ -H "Content-Type: application/json" \ -d '{ "amountCents": 10000, "description": "Venda compartilhada", "splits": [ { "recipientEmail": "vendedor1@example.com", "percentage": 50 }, { "recipientEmail": "vendedor2@example.com", "percentage": 50 } ], "customer": { "name": "Cliente", "email": "cliente@example.com" } }' ``` Response 201: ```json { "paymentId": "psplit_a1b2c3d4...", "status": "pending", "amountCents": 10000, "currency": "BRL", "environment": "live", "pix": { "brCode": "00020126360014BR.GOV.BCB.PIX...", "qrCodeImage": null }, "splits": [ { "recipientEmail": "ve***@example.com", "percentage": 50 }, { "recipientEmail": "ve***@example.com", "percentage": 50 } ], "expiresAt": "2026-05-28T12:30:00.000Z" } ``` Validation errors (HTTP 400) com `error` explicando o problema: - `splits must have at least 2 recipients` - `splits must have at most 10 recipients` - `splits[0].percentage must be >= 0.01 (got 0)` - `splits[1].percentage must be <= 99.99 (got 100). Use a single payment instead of split for 100%.` - `splits percentages must sum to exactly 100.00 (got 90.00)` - `splits[0].recipientEmail is duplicated` - `splits[1].recipientEmail not found in paysync` - `splits[0].recipientEmail cannot be the API key owner (would defeat the point)` - `amountCents exceeds R$ 5,000.00 cap for split charges` ### GET /v1/split-charges/:paymentId Consult status of a split charge. Response: ```json { "paymentId": "psplit_a1b2c3d4...", "status": "paid", "amountCents": 10000, "gatewayFeeCents": 250, "netAmountCents": 9750, "currency": "BRL", "environment": "live", "pix": { "brCode": "..." }, "splits": [ { "recipientEmail": "ve***@example.com", "percentage": 50, "amountCents": 4875, "creditedAt": "2026-05-28T12:15:00.000Z" }, { "recipientEmail": "ve***@example.com", "percentage": 50, "amountCents": 4875, "creditedAt": "2026-05-28T12:15:00.000Z" } ], "paidAt": "2026-05-28T12:15:00.000Z", "expiresAt": "2026-05-28T12:30:00.000Z", "createdAt": "2026-05-28T12:00:00.000Z" } ``` Status values: `pending` | `paid` | `expired` | `refunded` | `cancelled`. ### Webhook (callbackUrl) Se você passar `callbackUrl` no POST, paysync envia POST nesse URL quando o pagamento for confirmado, com HMAC signature (ver seção "Webhooks"). Payload inclui `paymentId`, `status: "paid"`, `splits` mascarados. ### Exemplo de distribuição Charge de R$ 100,00 (10000 cents), taxa de 2% + R$ 0,50 = R$ 2,50 (250 cents). Líquido = 9750 cents. Splits 33.33% / 33.33% / 33.34%: - A: floor(9750 * 0.3333) = 3249 cents (R$ 32,49) - B: floor(9750 * 0.3333) = 3249 cents (R$ 32,49) - C: 9750 - 3249 - 3249 = 3252 cents (R$ 32,52) Soma exata = 9750. Sem perda de centavo nem "sobra" em conta paysync. --- ## Card Payments (Stripe) Not available in sandbox mode. ### POST /v1/card-payments Create a card payment via Stripe Checkout. Request body (JSON): | Field | Type | Required | Rules | |---------------------|--------|----------|---------------------------------------| | valueCents | number | Yes | integer >= 100 | | description | string | No | max 200, default "Pagamento Cartao" | | callbackUrl | string | No | HTTPS URL, max 500 chars | | customer.name | string | No | max 100 chars | | customer.email | string | No | max 255 chars | | customer.externalId | string | No | max 200 chars | | metadata | string | No | JSON string, max 2048 chars | | successUrl | string | No | redirect URL after payment, max 500 | | cancelUrl | string | No | redirect URL on cancel, max 500 | Response (201): ```json { "orderCode": "JUE7Y9MPSX", "status": "pending", "amountCents": 4990, "currency": "BRL", "checkoutUrl": "https://checkout.stripe.com/...", "expiresAt": "2026-03-18T12:30:00.000Z" } ``` ### GET /v1/card-payments/:orderCode Get card payment details. Response (200): ```json { "orderCode": "JUE7Y9MPSX", "status": "paid", "amount": 49.90, "amountCents": 4990, "currency": "BRL", "description": "Pagamento Cartao", "checkoutUrl": null, "customer": { "name": "...", "email": "...", "externalId": "..." }, "metadata": null, "paidAt": "2026-03-18T12:05:00.000Z", "expiresAt": "2026-03-18T12:30:00.000Z", "createdAt": "2026-03-18T12:00:00.000Z" } ``` Status values: `pending`, `paid`, `expired`, `refunded`, `failed` ### GET /v1/card-payments List card payments. Query params: | Param | Type | Default | Rules | |--------|--------|---------|-------------------------------------------| | limit | number | 50 | 1–100 | | offset | number | 0 | >= 0 | | status | string | — | pending, paid, expired, refunded, failed | Response (200): ```json { "payments": [ { "orderCode": "...", "status": "...", "amountCents": 4990, "paidAt": null, "createdAt": "..." } ], "total": 10, "limit": 50, "offset": 0 } ``` --- ## Subscriptions (PIX recurring) ### POST /v1/subscriptions Create a recurring PIX subscription linked to a product. Request body (JSON): | Field | Type | Required | Rules | |--------------------|--------|----------|------------------------------------------| | productId | string | Yes | ObjectId of an active product | | customer.name | string | Yes | max 100 chars | | customer.externalId| string | No | max 200 chars | | callbackUrl | string | No | HTTPS URL, max 500 chars | | metadata | string | No | JSON string, max 2048 chars | | frequency | string | No | WEEKLY, MONTHLY (default), SEMIANNUALLY, ANNUALLY | | dayGenerateCharge | number | No | 4–28, day of month to generate charges | Response (200): ```json { "paymentId": "psa_sub_a1b2c3d4...", "status": "pending", "type": "subscription", "subscriptionId": "sub_xyz...", "amountCents": 4990, "currency": "BRL", "productName": "Premium Plan", "frequency": "MONTHLY", "environment": "live", "pix": { "brCode": "00020126580014br.gov.bcb.pix...", "paymentLinkUrl": "https://openpix.com.br/pay/..." }, "dayGenerateCharge": 15 } ``` ### GET /v1/subscriptions List subscriptions. Query params: | Param | Type | Default | Rules | |--------|--------|---------|--------------------------------------| | limit | number | 50 | 1–100 | | offset | number | 0 | >= 0 | | status | string | — | pending, paid, expired, refunded | Response (200): ```json { "subscriptions": [ { "paymentId": "psa_sub_a1b2c3d4...", "subscriptionId": "sub_xyz...", "status": "pending", "amountCents": 4990, "currency": "BRL", "productName": "Premium Plan", "customer": { "name": "João Silva", "email": "", "externalId": "user_123" }, "metadata": "{}", "paidAt": null, "createdAt": "2026-03-18T10:00:00.000Z" } ], "total": 5, "limit": 50, "offset": 0 } ``` --- ## Disputes (MED — Mecanismo Especial de Devolução) ### GET /v1/disputes List disputes (chargebacks/MEDs) against your account. Query params: | Param | Type | Default | Rules | |--------|--------|---------|------------------------------------| | limit | number | 50 | 1–100 | | offset | number | 0 | >= 0 | | status | string | — | aberta, resolvida, perdida | Response (200): ```json { "disputes": [ { "id": "665f1a2b3c4d5e6f7a8b9c0d", "code": "MED-2024-001", "wooviDisputeId": "abc123...", "endToEndId": "E123456782024...", "orderCode": "JUE7Y9MPSX", "buyer": "João Silva", "buyerDiscordId": "", "product": "Premium Plan", "amount": 23.07, "reason": "Produto não recebido", "status": "aberta", "evidences": [], "resolvedAt": null, "createdAt": "2026-03-18T10:00:00.000Z" } ], "total": 3, "limit": 50, "offset": 0 } ``` Returns empty list in sandbox mode. ### GET /v1/disputes/:id Get a single dispute by ID. Response (200): Single dispute object (same fields as above, with `evidences` array populated). ### POST /v1/disputes/:id/evidence Submit evidence to an open dispute. Not available in sandbox mode. Dispute must have status `aberta` and a valid `wooviDisputeId`. Request body (JSON): | Field | Type | Required | Rules | |-----------------------------|--------|-----------------------|---------------------------------------| | documents | array | Yes (or textForPdf) | max 10 items | | documents[].url | string | Yes | HTTP(S) URL, max 2000 chars | | documents[].description | string | No | max 500 chars, default "Evidence" | | documents[].correlationID | string | No | max 100 chars, auto-generated | | textForPdf | string | Yes (or documents) | Text to convert to PDF evidence | Response (200): ```json { "uploaded": 2 } ``` --- ## Sandbox (test environment) Use a `ps_test_` API key. No real charges are created. Data is fully isolated from production. ### POST /v1/sandbox/payments/:paymentId/simulate-paid Simulate a payment being paid. Triggers callback webhook if `callbackUrl` was set. Requires `ps_test_` key. Returns 403 with live key. Response (200): ```json { "success": true, "paymentId": "psa_a1b2c3d4...", "status": "paid", "paidAt": "2026-03-18T12:05:00.000Z" } ``` ### POST /v1/sandbox/charges/:paymentId/simulate-paid Same as above but for standalone charges (psc_ prefix). Response (200): ```json { "success": true, "paymentId": "psc_a1b2c3d4...", "status": "paid", "paidAt": "2026-03-18T12:05:00.000Z" } ``` ### GET /v1/sandbox/wallet Get simulated wallet balance. Response (200): ```json { "sandbox": true, "availableCents": 12990, "pendingCents": 4990, "available": 129.90, "pending": 49.90 } ``` ### GET /v1/sandbox/transactions List simulated transactions. Query params: | Param | Type | Default | Rules | |--------|--------|---------|---------| | limit | number | 50 | 1–200 | | offset | number | 0 | >= 0 | Response (200): ```json { "sandbox": true, "transactions": [ { "id": "...", "paymentId": "psa_abc123", "type": "one_time", "source": "payment", "status": "paid", "amountCents": 4990, "createdAt": "2026-03-18T10:00:00.000Z", "paidAt": "2026-03-18T10:05:00.000Z" } ], "total": 1, "limit": 50, "offset": 0 } ``` --- ## Webhooks ### Security: HMAC signature (applies to ALL webhooks) Every outbound webhook (payment.paid, charge.paid, card_payment.paid, order.paid, withdrawal.*) is signed with HMAC-SHA256 using your store's `webhookSecret`. The signature comes in the `X-Webhook-Signature` header on every request. There's no opt-in flag, it's always there. Get your secret: ``` GET https://api.usepaysync.com/api/developer/webhook-secret Authorization: Bearer → { "secret": "whk_a1b2c3d4..." } ``` Or in Dashboard → API & Desenvolvedores → Webhooks. Store it as an environment variable, never expose on the frontend. To rotate: POST /api/developer/webhook-secret/regenerate (old secret stops working immediately). Validation (Node.js / Express, raw body required): ```js import crypto from "node:crypto"; import express from "express"; app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => { const signature = req.header("X-Webhook-Signature"); const expected = crypto .createHmac("sha256", process.env.PAYSYNC_WEBHOOK_SECRET) .update(req.body) .digest("hex"); const valid = signature && signature.length === expected.length && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); if (!valid) return res.status(401).send("invalid signature"); const event = JSON.parse(req.body.toString()); // ... process event res.json({ ok: true }); }); ``` Validation (PHP): ```php $payload = file_get_contents('php://input'); $signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? ''; $expected = hash_hmac('sha256', $payload, getenv('PAYSYNC_WEBHOOK_SECRET')); if (!hash_equals($expected, $signature)) { http_response_code(401); exit; } $event = json_decode($payload, true); ``` Reject any webhook with an invalid or missing signature. Public callback URLs can be hit by anyone, and without HMAC validation a forged "paid" event passes through. ### payment.paid callback Sent as POST to the `callbackUrl` you set when creating a Payment Link (POST /v1/payments). Fire-and-forget (single attempt, 5s timeout). To confirm, use GET /v1/payments/:paymentId. Payload: ``` POST https://seu-site.com/webhook Content-Type: application/json X-Webhook-Signature: a3f5b8c2e1d4f7a9b6c8d2e5f1a4b7c9d2e5f8a1b4c7d0e3f6a9b2c5d8e1f4a7 { "event": "payment.paid", "paymentId": "psa_a1b2c3d4...", "amountCents": 4990, "status": "paid", "paidAt": "2026-03-18T12:05:00.000Z", "customer": { "name": "João Silva", "email": "joao@email.com", "externalId": "user_123" }, "metadata": "{\"orderId\": \"abc-123\"}" } ``` ### charge.paid callback Sent as POST to the `callbackUrl` of a PIX charge (POST /v1/charges). Same shape as payment.paid but with extra fields from the acquirer. Payload: ```json { "event": "charge.paid", "paymentId": "psa_a1b2c3d4...", "amountCents": 4990, "status": "paid", "paidAt": "2026-03-18T12:05:00.000Z", "customer": { "name": "João Silva", "email": "joao@email.com" }, "metadata": "{\"orderId\": \"abc-123\"}", "payer": "João Silva", "cpfCensored": "123.***.**1-23", "bank": "077 - Banco Inter", "endToEndId": "E1818773820260318120500abc12345" } ``` ### card_payment.paid callback Sent when a card charge (Stripe) is confirmed. Payload uses `amount` (BRL reais, not centavos) and `orderCode` instead of `paymentId`. Payload: ```json { "event": "card_payment.paid", "orderCode": "ORD-7K3B9X", "amount": 49.90, "status": "paid", "paidAt": "2026-03-18T12:05:00.000Z", "customer": { "name": "João Silva", "email": "joao@email.com" }, "metadata": "{\"orderId\": \"abc-123\"}" } ``` ### order.paid (Discord store webhook) For Discord stores using the PaySync bot. When a bot-issued order is paid, we POST to the URL configured in store setting `order_webhook_url`. Per-store (not per-charge). Payload: ```json { "event": "order.paid", "orderId": "665f1a2b3c4d5e6f7a8b9c0d", "orderCode": "ORD-7K3B9X", "total": 49.90, "paidAt": "2026-03-18T12:05:00.000Z", "acquirer": "cartwave" } ``` ### Withdrawal webhooks Sent as POST to the URL configured in dashboard settings (withdrawal_webhook_url). Same HMAC validation as everything else (see Security section above). Events: - `withdrawal.requested` — withdrawal created, status: pendente - `withdrawal.completed` — withdrawal approved, status: concluido - `withdrawal.denied` — withdrawal denied, status: negado Payload example (withdrawal.requested): ```json { "event": "withdrawal.requested", "withdrawalId": "665f1a2b3c4d5e6f7a8b9c0d", "code": "SAQ-A1B2C3", "amount": 150.00, "method": "pix", "walletAddress": "email@exemplo.com", "status": "pendente", "requestedAt": "2026-03-18T14:00:00.000Z" } ``` Payload example (withdrawal.completed): ```json { "event": "withdrawal.completed", "withdrawalId": "665f1a2b3c4d5e6f7a8b9c0d", "code": "SAQ-A1B2C3", "amount": 150.00, "method": "pix", "walletAddress": "email@exemplo.com", "status": "concluido", "processedAt": "2026-03-18T15:30:00.000Z" } ``` ### Sandbox withdrawal webhooks In sandbox mode, withdrawals are auto-approved. Events use different names: - `withdrawal.test.requested` - `withdrawal.test.completed` Payload includes `"sandbox": true`. --- ## Deliveries (stock delivery status) ### GET /v1/deliveries/:paymentId Get delivery status of a paid payment. Returns delivered content if available. Path param: `paymentId` (string, psa_ or psc_ prefix) Response (200): ```json { "paymentId": "psa_a1b2c3d4...", "status": "paid", "amountCents": 4990, "paidAt": "2026-03-18T12:05:00.000Z", "deliveredContent": "LICENSE-KEY-ABC-123", "customer": { "name": "Joao Silva", "email": "joao@email.com", "externalId": "user_123" } } ``` `deliveredContent` is null if the payment is pending or the product has manual delivery. --- ## Currency Conversion If a product uses a non-BRL currency (e.g., USD, EUR), the API automatically converts the amount to BRL using the current exchange rate when creating a PIX charge. The mid-market rate (average of bid/ask) is fetched from the Brazilian Central Bank and cached for 5 minutes. Example: Product priced at $10.00 USD with rate 5.80 = PIX charge of R$ 58.00. The response always shows `amountCents` in BRL (the actual charge amount). Supported currencies: BRL (default), USD, EUR, GBP. --- ## Supplier Splits (optional) POST /v1/payments and POST /v1/charges accept optional supplier fields: | Field | Type | Required | Rules | |-----------------------------|--------|----------|---------------------------------------| | supplier.productId | string | No | Must start with "prod_" prefix | | supplier.variationIndex | number | No | default 0 | When a supplier product is linked, the payment amount is split between the store and the supplier according to the configured split (fixed or percentage). The store receives the remainder after the supplier's share and gateway fees. --- ## Payouts (withdrawals via API) Request a withdrawal of available balance via API. Funds are deducted atomically. ### POST /v1/payouts — Request withdrawal **Rate limit:** 10 requests per hour per API key. **Headers:** - `Authorization: Bearer ps_live_` (required) - `Content-Type: application/json` **Body:** | Field | Type | Required | Description | |---------------|--------|----------|--------------------------------------------------| | method | string | Yes | `"pix"` or `"ltc"` | | amount | number | Yes | Amount in BRL (min 5.00, max 999,999.99) | | walletAddress | string | Yes | PIX key (for PIX) or LTC address (for LTC) | | cryptoAmount | number | LTC only | Amount in LTC (required when method is `"ltc"`) | **Response (201):** ```json { "id": "SAQ-API-A1B2C3D4", "code": "SAQ-API-A1B2C3D4", "method": "pix", "amount": 100.00, "status": "pending", "sandbox": false } ``` **Status values:** `pending` (awaiting admin approval), `completed`, `denied`. **Error codes:** - 400: Invalid params, insufficient balance, amount out of range - 403: PIX not verified (must complete verification in dashboard first) - 429: Rate limit exceeded (max 10/hour) **Security:** - Atomic balance decrement (prevents double-spend) - PIX key must be pre-verified in dashboard - LTC address validated by format regex - Wallet address truncated in logs/events (no PII leak) - Sandbox mode: returns simulated response, no real balance change ### GET /v1/payouts — List withdrawals **Query params:** | Param | Type | Default | Description | |--------|--------|---------|------------------------------------------| | limit | number | 20 | Max results (1-100) | | status | string | all | Filter: `pendente`, `concluido`, `negado`| **Response:** ```json { "payouts": [ { "id": "SAQ-API-A1B2C3D4", "code": "SAQ-API-A1B2C3D4", "method": "pix", "amount": 100.00, "cryptoAmount": null, "walletAddress": "12345***", "status": "pendente", "createdAt": "2026-04-27T12:00:00.000Z" } ] } ``` **Note:** `walletAddress` is always masked in the response (first 6 chars + ***). --- ## Quick Reference — All Endpoints | Method | Path | Description | |--------|-------------------------------------------------|--------------------------------| | POST | /v1/products | Create product | | GET | /v1/products | List products | | GET | /v1/products/:id | Get product | | PUT | /v1/products/:id | Update product | | DELETE | /v1/products/:id | Delete product | | POST | /v1/payments | Create payment (PIX or LTC) | | GET | /v1/payments/:paymentId | Get payment | | GET | /v1/payments | List payments | | POST | /v1/charges | Create standalone PIX charge | | GET | /v1/charges/:paymentId | Get charge | | POST | /v1/card-payments | Create card payment (Stripe) | | GET | /v1/card-payments/:orderCode | Get card payment | | GET | /v1/card-payments | List card payments | | POST | /v1/subscriptions | Create subscription | | GET | /v1/subscriptions | List subscriptions | | GET | /v1/deliveries/:paymentId | Get delivery status | | GET | /v1/disputes | List disputes | | GET | /v1/disputes/:id | Get dispute | | POST | /v1/disputes/:id/evidence | Submit dispute evidence | | POST | /v1/sandbox/payments/:paymentId/simulate-paid | Simulate payment paid | | POST | /v1/sandbox/charges/:paymentId/simulate-paid | Simulate charge paid | | GET | /v1/sandbox/wallet | Sandbox wallet balance | | POST | /v1/payouts | Request withdrawal (PIX/LTC) | | GET | /v1/payouts | List withdrawals | | GET | /v1/sandbox/transactions | Sandbox transactions | --- ## Integration Patterns ### Basic PIX payment flow: 1. POST /v1/payments → get `paymentId` and `pix.brCode` 2. Show QR code / copy-paste brCode to customer 3. Receive webhook at `callbackUrl` when paid (or poll GET /v1/payments/:paymentId) 4. Deliver product/service ### LTC (Litecoin) payment flow: 1. POST /v1/payments with `"paymentMethod": "ltc"` → get `paymentId` and `ltc.address` + `ltc.amount` 2. Show LTC address and amount to customer 3. Receive webhook at `callbackUrl` when confirmed on blockchain (6 confirmations) 4. GET /v1/payments/:paymentId → status will be `"paid"` when confirmed 5. LTC not available in sandbox mode ### Sandbox testing flow: 1. Create API key with `ps_test_` prefix in dashboard 2. POST /v1/payments with test key → get sandbox payment 3. POST /v1/sandbox/payments/:paymentId/simulate-paid → mark as paid 4. Verify webhook was sent to your callbackUrl 5. GET /v1/sandbox/wallet → check simulated balance ### Subscription flow: 1. POST /v1/products → create product with price 2. POST /v1/subscriptions → create recurring subscription 3. Customer pays first charge via PIX 4. Subsequent charges auto-generated on `dayGenerateCharge` ### Card payment flow: 1. POST /v1/card-payments → get `checkoutUrl` 2. Redirect customer to Stripe Checkout URL 3. Receive webhook when paid 4. GET /v1/card-payments/:orderCode → verify status