MagicPayments Integration Guides

Brazil Pix Gate

Pay-inPay-outBRLPix

Accept Pix pay-ins on a hosted QR page and send Pix payouts to a CPF key in Brazilian Real (BRL).

When to use it

The Brazil Pix Gate accepts Pix pay-ins on a MagicPayments-hosted page — the payer enters their identification (name, CPF, e-mail, phone), then the page shows a Pix QR code and the "Pix Copia e Cola" string; the payer completes the transfer in their banking app — and sends Pix payouts to a CPF Pix key. All amounts are in BRL (Brazilian Real, 2 decimal places — so 10000 means 100.00 BRL). Individuals only: payers and payout recipients are identified by CPF (CNPJ is not supported).

OperationRailHow the integration looks
Pay-in (hosted)Pix (QR / Copia e Cola)Create an invoice, redirect the payer to the hosted page.
Pay-in (H2H)Pix (QR / Copia e Cola)You render your own form and QR — send us the payer's CPF, poll for the Pix code.
Pay-outPix key (CPF)Server-to-server call to the wallet-account payout endpoint.
Limits

Pay-ins and payouts accept 50 – 5,000 BRL per operation. In minor units that is 5000500000.

Prerequisites

You need your signing credentials from Getting started, a pay-in gate_id for Brazil Pix, and a pay-out cascade_id enabled for BRL Pix payouts. Your account manager provides both. First-time and repeat depositors may be routed through separate gates — if so, your account manager gives you two gate_ids and tells you which is which.

Pay-in — hosted Pix QR page

The payer never touches your servers with their payment details. You create an invoice; we host the page.

  1. Create the invoice Call the gate endpoint with your gate_id and the amount in BRL minor units.
  2. Redirect the payer Send the customer's browser to the invoice_url we return. Our page collects the payer's name, CPF, e-mail and phone, then shows the Pix QR code and the "Copia e Cola" string with a countdown; the payer completes the transfer in their banking app.
  3. Receive the result The transfer is matched automatically — the payer does not confirm anything on the page. We call your callback_url on every status change; you can poll invoice status as a fallback.
POST/api/invoice
Request body
{
  "gate_id": "br-pix-gate",
  "invoice": {
    "invoice_id": "order-br-2026-000123",
    "currency": "BRL",
    "amount": 10000,
    "description": "Wallet top-up",
    "ttl_minutes": 30
  },
  "customer": {
    "id": "cust-88130",
    "full_name": "Joao Silva",
    "phone_number": "+5511999999999"
  },
  "workflow_hooks": {
    "callback_url": "https://merchant.example.com/mp/callbacks",
    "return_success_url": "https://merchant.example.com/orders/123/done",
    "return_decline_url": "https://merchant.example.com/orders/123/retry"
  }
}
Python
resp = signed_post("/api/invoice", {
    "gate_id": "br-pix-gate",
    "invoice": {
        "invoice_id": "order-br-2026-000123",
        "currency": "BRL",
        "amount": 10_000,             # 100.00 BRL (2 decimal places)
        "description": "Wallet top-up",
        "ttl_minutes": 30,
    },
    "customer": {
        "id": "cust-88130",
        "full_name": "Joao Silva",
        "phone_number": "+5511999999999",
    },
    "workflow_hooks": {
        "callback_url": "https://merchant.example.com/mp/callbacks",
        "return_success_url": "https://merchant.example.com/orders/123/done",
        "return_decline_url": "https://merchant.example.com/orders/123/retry",
    },
})
invoice = resp.json()
redirect_url = invoice["invoice_url"]   # send the payer here
Response
{
  "request_id": "0f0b1d2e-...",
  "request_status": "success",
  "invoice_id": "62051f64-9a79-4edb-8a9e-95c86c55ee4e",
  "merchant_invoice_id": "order-br-2026-000123",
  "invoice_status": "unpaid",
  "invoice_url": "https://stage.example-mp.com/public/invoice/62051f64-.../gate",
  "message": "Invoice (Gate PayIn) with internal uid=62051f64-... has been created."
}
The QR code expires

The Pix code our page shows is valid for a limited window (typically ~10 minutes) and the page shows the payer a countdown. Set a ttl_minutes that gives the payer time to complete the transfer in their banking app, and lean on the callback rather than tight polling.

The payer must use their own account

The Pix transfer must come from an account linked to the CPF the payer entered on the page. A transfer from someone else's account may not be matched to the payment.

Tracking the pay-in

Look up the invoice by your own id (or our invoice_uid). It reaches paid on success.

POST/api/invoice/status
resp = signed_post("/api/invoice/status", {
    "merchant_invoice_id": "order-br-2026-000123"
})
invoice = resp.json()
# invoice["status"] -> "paid" | "payment_failed" | "expired" | "canceled" | ...
# invoice["actual_payment"]["processing_info"]["amount_acquired"] -> settled BRL minor units

Pay-in — H2H (your own form)

If you collect the payer's details on your own page, skip the invoice and create the payment directly. You must send the payer's CPF in customer.document — there is no hosted form to collect it, and the Pix transfer must come from an account linked to that CPF. The request is validated synchronously: a missing or checksum-invalid CPF is rejected with 400 before a payment is created.

POST/api/payment/payin/pix
Request body
{
  "cascade_id": "br-pix-h2h",
  "payment": {
    "payment_id": "order-br-2026-000124",
    "currency": "BRL",
    "amount": 10000,
    "description": "Wallet top-up"
  },
  "customer": {
    "id": "cust-88130",
    "full_name": "Joao Silva",
    "document": "12345678909",
    "email": "[email protected]",
    "phone_number": "+5511999999999",
    "ip_address": "203.0.113.10"
  },
  "workflow_hooks": {
    "callback_url": "https://merchant.example.com/mp/callbacks"
  }
}
customer.document

customer.document is the payer's CPF — 11 digits, formatted input (123.456.789-09) is accepted. customer.document_type may be omitted or set to "CPF". Send the payer's real ip_address and contact details when you have them — they feed the rail's antifraud checks.

Getting the Pix code

The QR requisites are assigned asynchronously, typically within a few seconds. Poll the payment status endpoint until payment_details appears, then render qr_data — it is both the "Copia e Cola" string and the QR payload (encode it as a QR image yourself).

POST/api/payment/status
resp = signed_post("/api/payment/status", {
    "merchant_payment_id": "order-br-2026-000124"
})
payment = resp.json()
details = payment.get("payment_details") or {}
pix_code = details.get("qr_data")           # "Copia e Cola" string == QR payload
expires_at = details.get("expires_at")      # ISO timestamp; show the payer a countdown
amount = details.get("amount_display")      # BRL major units; may be adjusted — always render this one
Render the amount we report

The rail may slightly adjust the payable amount to disambiguate concurrent transfers. Always display amount_display from the status response rather than your original request amount — a payer typing the wrong sum will not be matched.

Show the deadline

The Pix code expires (typically ~10 minutes from assignment). Render the countdown from expires_at and lean on the callback for the final status — the transfer is matched automatically, there is no confirmation step.

Pay-out — to a CPF Pix key

Payouts go server-to-server. The destination is the recipient's CPF used as their Pix key (11 digits, no dots or dashes). Use the wallet-account payout endpoint with a cascade_id enabled for BRL payouts.

POST/api/payment/payout/wallet_account
Request body
{
  "cascade_id": "br-pix-payout",
  "payment": {
    "payment_id": "payout-br-2026-000045",
    "currency": "BRL",
    "amount": 5000,
    "description": "Affiliate payout"
  },
  "wallet": {
    "provider": "pix",
    "wallet_number": "12345678909",
    "full_name": "Richard Roe"
  },
  "customer": {
    "id": "partner-2391",
    "full_name": "Richard Roe",
    "phone_number": "+5511999999999"
  },
  "workflow_hooks": {
    "callback_url": "https://merchant.example.com/mp/callbacks"
  }
}
CPF keys only

wallet.wallet_number must be the recipient's CPF (11 digits). Phone, e-mail and random (EVP) Pix keys are not supported on this rail, and payouts to legal entities (CNPJ) are not accepted. wallet.full_name is the recipient's name and is required.

Python
resp = signed_post("/api/payment/payout/wallet_account", {
    "cascade_id": "br-pix-payout",
    "payment": {
        "payment_id": "payout-br-2026-000045",
        "currency": "BRL",
        "amount": 5_000,              # 50.00 BRL — the payout minimum
        "description": "Affiliate payout",
    },
    "wallet": {
        "provider": "pix",
        "wallet_number": "12345678909",   # recipient CPF, digits only
        "full_name": "Richard Roe",
    },
    "customer": {
        "id": "partner-2391",
        "full_name": "Richard Roe",
        "phone_number": "+5511999999999",
    },
    "workflow_hooks": {"callback_url": "https://merchant.example.com/mp/callbacks"},
})
payout = resp.json()
payout_uid = payout["payment_id"]   # our uid; status starts at "initiated"

Tracking the pay-out

Payouts use the payment status endpoint (not the invoice one). The reason for a rejected payout is delivered with the decline status in your callback.

POST/api/payment/status
resp = signed_post("/api/payment/status", {
    "merchant_payment_id": "payout-br-2026-000045"
})
# resp.json()["status"] -> "success" | "processing" | "decline" | "error"

Testing & go-live