EUR → BRL payout
A Spanish business needs to pay a supplier in Brazil. You collect euros locally in Spain, and the beneficiary receives Brazilian reais over PIX. Settlement between the two sides is handled internally — you never touch it. This recipe walks the whole corridor end to end.
EUR in Spain
Pay out BRL via PIX
Settlement internal · automatic
Prerequisites
You'll need workspace credentials and a short-lived bearer token. Every request below assumes $VB_TOKEN holds a valid access token — see the Authentication guide for how to obtain and refresh one.
Run the whole flow against https://api-stage.virtuabroker.com first. Stage behaves like production but moves no real money, so you can drive the corridor as many times as you need before going live.
Step-by-step walkthrough
Six steps take you from an empty workspace to a settled BRL payout: discover the destination shape, lock a rate, place the order, fund the euro side, react to the webhook, and reconcile.
Discover the destination fields
Every payout currency has its own destination shape. Ask the API what a BRL payout requires instead of hard-coding it — the schema is the source of truth and can gain fields over time.
curl https://api.virtuabroker.com/v1/currencies/BRL/destination-schema \ -H "Authorization: Bearer $VB_TOKEN"
{
"currency": "BRL",
"rail": "instant_credit",
"fields": [
{ "name": "destinationName", "type": "string", "required": true },
{ "name": "destinationPixCode", "type": "string", "required": true },
{ "name": "destinationTaxId", "type": "string", "required": true }
]
}
For BRL you'll send the beneficiary's destinationName, their destinationPixCode (the PIX key), and their destinationTaxId (the recipient's CPF/CNPJ) inside destinationTransferData — all three are required. Discover the shape from the API rather than hard-coding it: the schema is the source of truth and can gain fields over time.
Lock a quote
A quote fixes the rate and fees for the pair for a short window. Quote from the euro side you're collecting: pass originAmount in EUR and let the API return how many reais the beneficiary receives.
curl https://api.virtuabroker.com/v1/quotes \ -H "Authorization: Bearer $VB_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "originCurrency": "EUR", "destinationCurrency": "BRL", "originAmount": 5000 }'
const res = await fetch("https://api.virtuabroker.com/v1/quotes", { method: "POST", headers: { Authorization: `Bearer ${access_token}`, "Content-Type": "application/json" }, body: JSON.stringify({ originCurrency: "EUR", destinationCurrency: "BRL", originAmount: 5000 }), }); const quote = await res.json();
quote = requests.post("https://api.virtuabroker.com/v1/quotes", headers={"Authorization": f"Bearer {access_token}"}, json={"originCurrency": "EUR", "destinationCurrency": "BRL", "originAmount": 5000}, ).json()
{
"quoteId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"originCurrency": "EUR",
"originAmount": 5000,
"destinationCurrency": "BRL",
"destinationAmount": 30543.52,
"pair": "EURBRL",
"price": 6.1087,
"conversionRate": 6.1234,
"fee": 12,
"feeCurrency": "EUR",
"expireAt": 1694291200
}
Hold onto quoteId (a UUID) — the beneficiary will receive destinationAmount in BRL. Amounts are JSON numbers, never strings. Create the order before expireAt (a unix timestamp in seconds), or lock a fresh quote.
Create the order
Commit the quote to a real destination. Combine quoteId with the PIX fields you discovered in step 1 under destinationTransferData — set its type to PIX_BR. Because the euro side is funded over SEPA (not a workspace balance), you must also include userData — the payer — so the deposit can be provisioned; if the payer email is new, send their address and name fields too. Tag the order with your own externalReference; order reads return it as externalRefeference (note the spelling). Writes are not de-duplicated, so if this call times out, reconcile on that field before resending rather than risking a doubled payout (see Retrying a write).
curl https://api.virtuabroker.com/v1/orders \ -H "Authorization: Bearer $VB_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "quoteId": "d290f1ee-6c54-4b01-90e6-d701748f0851", "userData": { "userEmail": "payer@example.com", "firstName": "Lucía", "lastName": "García", "address": "Calle Gran Vía 12", "city": "Madrid", "postalCode": "28013", "state": "Madrid", "country": "ES", "taxId": "12345678Z" }, "destinationTransferData": { "type": "PIX_BR", "destinationName": "Fornecedor Brasil LTDA", "destinationPixCode": "supplier@example.com", "destinationTaxId": "12.345.678/0001-95" }, "externalReference": "supplier-inv-8842" }'
const res = await fetch("https://api.virtuabroker.com/v1/orders", { method: "POST", headers: { Authorization: `Bearer ${access_token}`, "Content-Type": "application/json" }, body: JSON.stringify({ quoteId: "d290f1ee-6c54-4b01-90e6-d701748f0851", userData: { userEmail: "payer@example.com", firstName: "Lucía", lastName: "García", address: "Calle Gran Vía 12", city: "Madrid", postalCode: "28013", state: "Madrid", country: "ES", taxId: "12345678Z", }, destinationTransferData: { type: "PIX_BR", destinationName: "Fornecedor Brasil LTDA", destinationPixCode: "supplier@example.com", destinationTaxId: "12.345.678/0001-95" }, externalReference: "supplier-inv-8842", }), }); const order = await res.json();
order = requests.post("https://api.virtuabroker.com/v1/orders", headers={"Authorization": f"Bearer {access_token}"}, json={ "quoteId": "d290f1ee-6c54-4b01-90e6-d701748f0851", "userData": { "userEmail": "payer@example.com", "firstName": "Lucía", "lastName": "García", "address": "Calle Gran Vía 12", "city": "Madrid", "postalCode": "28013", "state": "Madrid", "country": "ES", "taxId": "12345678Z", }, "destinationTransferData": {"type": "PIX_BR", "destinationName": "Fornecedor Brasil LTDA", "destinationPixCode": "supplier@example.com", "destinationTaxId": "12.345.678/0001-95"}, "externalReference": "supplier-inv-8842", }, ).json()
{
"orderId": "A7K9DP2X4Q1M",
"status": "Pending",
"createdAt": 1694290300,
"fee": 0,
"feeCurrency": "EUR",
"deposit": {
"status": "Pending",
"depositInfo": {
"depositType": "SEPA",
"iban": "ES00 0000 0000 0000 0000 0000",
"referenceCode": "VB-8F3K2P",
"amount": 5000
}
}
}
The order opens in Pending — the payout is armed but held until the euro side is funded. deposit.depositInfo tells your customer exactly where to send the EUR: the iban to pay and the referenceCode to quote.
At creation the order's fee is 0 — the final fees aren't known yet. They populate as the order executes; read the authoritative fee from a GET /v1/orders/{orderId} when you reconcile.
Fund the collection
The EUR side is collected through a deposit. Give the paying business the deposit.depositInfo from the order response: they transfer the euros to that IBAN, quoting the supplied referenceCode. When the funds land, the deposit moves from Pending to Success, the workspace balance is credited automatically, and the order moves from Pending to Processing — no extra API call is needed from you.
Local bank transfers can take from minutes to a business day depending on the sending bank. Treat funding as asynchronous and let the webhook (next step) drive your side. For the full deposit object and its states, see deposits in the API reference.
React to webhooks
Rather than polling, subscribe to order_status_updated — it fires on every status change, so check data.status and act when it reaches Success or Failed. Endpoints you register through POST /v1/webhooks receive a signed X-VirtuaBroker-Signature-256 header — verify it before you trust the body. See the Webhooks guide for the verification recipe.
{
"id": "A7K9DP2X4Q1M",
"type": "order_status_updated",
"api_version": "1.0",
"data": {
"orderId": "A7K9DP2X4Q1M",
"status": "Success"
}
}
Always verify the signature and treat handlers as idempotent — deliveries can repeat. The envelope id is the order’s id and is the same on every update for that order, so don’t treat a repeated id as a duplicate — reconcile the reported data.status against your ledger, and use a GET as the source of truth.
Reconcile
Match the event back to your ledger on orderId, which you stored against the externalReference you set at order creation, then confirm the final state authoritatively with a direct read. The GET is the source of truth if a webhook is ever delayed or missed.
curl https://api.virtuabroker.com/v1/orders/A7K9DP2X4Q1M \ -H "Authorization: Bearer $VB_TOKEN"
Full status flow
The API reports a EUR → BRL order with the same small set of statuses it uses for every order. It starts in Pending while the euro collection is awaited; once the deposit credits, it moves to Processing while the exchange and the BRL payout over PIX run; then it ends in Success. If a step can't be finished it ends in Failed; a cancelled order ends in Canceled. (An order held for compliance shows Reviewing.) The internal exchange/payout steps are collapsed into Processing — see the Statuses reference.