Brazil Pix Gate
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).
| Operation | Rail | How 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-out | Pix key (CPF) | Server-to-server call to the wallet-account payout endpoint. |
Pay-ins and payouts accept 50 – 5,000 BRL per operation. In minor units that is
5000–500000.
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.
-
Create the invoice
Call the gate endpoint with your
gate_idand the amount in BRL minor units. -
Redirect the payer
Send the customer's browser to the
invoice_urlwe 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. -
Receive the result
The transfer is matched automatically — the payer does not confirm anything on the page. We call
your
callback_urlon every status change; you can poll invoice status as a fallback.
/api/invoice{
"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 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 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.
/api/invoice/statusresp = 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.
/api/payment/payin/pix{
"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 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).
/api/payment/statusresp = 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
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.
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.
/api/payment/payout/wallet_account{
"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"
}
}
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.
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.
/api/payment/statusresp = signed_post("/api/payment/status", {
"merchant_payment_id": "payout-br-2026-000045"
})
# resp.json()["status"] -> "success" | "processing" | "decline" | "error"
Testing & go-live
- On stage, use the test
gate_id/cascade_idfrom your account manager; no real funds move. - Verify you can drive a pay-in to
paidand a payout tosuccess, and that your callback handler verifies the signature and is idempotent. - Confirm your BRL amount conversion:
amountis minor units with 2 decimal places. - Stay inside the limits: 50–5,000 BRL per operation for both directions.
- Validate CPFs before submitting payouts (11 digits, checksum) — an invalid or non-Pix-enabled CPF is the most common cause of a
decline. - Swap base URL and credentials to production; the request shapes are identical.