How to invoice in BRL or MXN and settle in USDC: FX between invoice and payment

Bill clients in reais or pesos and receive USDC. Where the rate gets fixed, who carries FX between invoice and payment, and three ways to price an invoice.

To invoice in BRL or MXN and settle in USDC, issue the invoice in local currency, then create a payin quote for that amount at the moment the client pays. The client pays by Pix or SPEI, and the reais or pesos convert to USDC at the quoted rate. The rate is set at payment time, so you carry FX between invoice and payment.

That gap is the whole problem. An invoice lives for 15, 30, or 60 days. A quote lives for 5 minutes. This guide covers where the rate actually gets fixed, three ways to price the invoice, and the API details that trip up invoice flows.

Key takeaways

  • A payin quote locks the numbers for 5 minutes. Create it when the client is ready to pay, not when you issue the invoice.
  • On a local currency invoice, the USDC you receive floats until payment. That's your FX exposure.
  • Fix the side that matters: invoice in BRL or MXN and accept a floating USDC amount, or invoice in USD and let the payer's local amount float.
  • Use cover_fees: false so the payer pays the invoice total exactly.
  • MXN, COP, and ARS amounts must be whole pesos when you quote the local side.

Why can't the invoice lock the exchange rate?

Because a payin quote expires 5 minutes after creation, and the invoice is open for weeks. BlindPay's payin quotes lock how much fiat the sender pays, how much stablecoin the destination receives, and the fee split, but only for that short window. You then create the payin from the quote and show the payer their instructions.

So the rate on an invoice is never the rate on the day you sent it. It's the rate on the day the client pays. If you need a refresher on which number in a quote is the rate and which way it points, how to read an FX quote covers base and quote currency.

This is the on-ramp version of a problem payout teams know well: the FX gap between a sale and a payout. Same mechanics, opposite direction.

Who carries FX risk between invoice and payment?

Whoever has a fixed amount in the other currency. The table shows the three common setups.

Invoice setupWhat is fixedWhat floatsWho carries the FX move
Invoice in BRL or MXN, quote the local sideThe local amount the client paysUSDC you receiveYou
Invoice in USD, quote the stablecoin sideUSDC you receiveLocal amount the client paysThe client
Invoice in local currency with a re-price clauseLocal amount, inside a bandUSDC, inside the bandShared

Local clients usually prefer the first setup. Their accounting is in reais or pesos, and a fixed local total is easy to approve. You pay for that convenience with exposure. Brazil's and Mexico's currencies can move several percent in a month, and live quotes on volatile currencies shows how quotes behave when they do.

How do you price the invoice in each setup?

Pick the currency_type that matches the side you fixed. On a payin quote, the convention is the opposite of a payout quote, which catches people.

currency_type on a payin quoterequest_amount is inUse it when
senderThe fiat the payer sends (BRL for Pix, MXN for SPEI)The invoice total is in local currency
receiverThe stablecoin the destination wallet receivesThe invoice total is in USD

Setup one, a BRL invoice of R$ 27,000.00: quote with currency_type: "sender", request_amount: 2700000, payment_method: "pix". The payer pays exactly R$ 27,000.00 and you receive whatever USDC the quote returns.

Setup two, a USD invoice of $5,000.00: quote with currency_type: "receiver", request_amount: 500000. The quote tells the payer how many reais to send for you to receive 5,000.00 USDC.

Amounts are integers in minor units, so 2700000 is R$ 27,000.00. Floats are rejected.

Illustrative example: what does a 19-day delay cost on a BRL invoice?

You invoice a Brazilian client R$ 27,000.00 on October 1. The market sits at 5.40 BRL per USD, so you expect about $5,000. The client pays on October 20, when the market is at 5.55. All rates below are illustrative and expressed as BRL per USDC.

LineValue
Invoice totalR$ 27,000.00
Expected at invoice date (27,000 / 5.40)5,000.00 USDC
Market value at payment date (27,000 / 5.55)4,864.86 USDC
Quoted rate after margin5.58 BRL per USDC
Converted at quoted rate (27,000 / 5.58)4,838.71 USDC
Flat fee, paid by you (cover_fees: false)1.00 USDC
receiver_amount4,837.71 USDC

Break down the 162.29 USDC shortfall against the 5,000 you expected:

  • FX move between invoice and payment: 5,000.00 - 4,864.86 = 135.14 USDC.
  • Rate margin at payment: 4,864.86 - 4,838.71 = 26.15 USDC.
  • Flat fee: 1.00 USDC.

Total: 135.14 + 26.15 + 1.00 = 162.29 USDC. The quote's cost is visible and small. The FX move is the real exposure, and the quote can't protect you from a move that happened before it existed.

If that exposure bothers you, the honest fixes are setup two (bill in USD), a shorter payment term, or a natural hedge against BRL costs you already pay.

What does the payment flow look like for Pix and SPEI?

Three calls, timed to the payer, not the invoice.

  1. Create the payin quote when the client clicks pay. Pass the destination (blockchain_wallet_id or wallet_id), payment_method, currency_type, cover_fees, request_amount, and token.
  2. Create the payin from the quote within 5 minutes. POST /v1/instances/{instance_id}/payins/evm with the quote id. The path name is historical and works for every rail (payins).
  3. Show the instructions. For Pix, the response carries a pix_code to show as copyable text or a QR code. For SPEI, it carries a CLABE.

Once the payin exists, BlindPay waits for the deposit. Pix and SPEI typically settle within minutes, and BlindPay waits up to 30 minutes before marking a standard payin failed (cut-off times). Pix runs every day of the year (Banco Central do Brasil), and Banco de México says SPEI transfers should not take more than 30 seconds after approval (Banxico).

A payin can't be canceled after creation. Create it only when the client is actually paying, which is one more reason to quote at the pay button, not at invoice time.

Which API details break invoice flows?

Four, in order of how often they show up.

Fees on top of the invoice. On a payin quote, cover_fees: true adds the fee to the fiat the sender pays. The client then sends more than the invoice total, and their accounts payable team flags it. Use cover_fees: false and the fee comes out of the USDC you receive.

Centavos on Mexican invoices. When currency_type is sender and the currency is MXN, COP, or ARS, request_amount must be a multiple of 100. 10050 fails with request_amount_must_be_a_whole_currency_unit. Round invoice totals to whole pesos. On the receiver side, BlindPay rounds the computed peso amount down to a whole peso for you.

The wrong payer. Pix quotes accept payer_rules.pix_allowed_tax_ids, a list of CPF or CNPJ numbers allowed to pay. Put the client's CNPJ there, and a payment from some other account won't be matched to your invoice. BlindPay checks the tax id's check digits at quote time.

Amount limits. Each currency has a minimum and maximum, and the quote enforces them with an error that names the range. Don't hardcode a floor. Why on-ramp deposits fail covers what happens when a payer sends the wrong amount or pays late.

Should you just quote at invoice time and hope?

No. A quote created on invoice day expires 5 minutes later, so there's nothing to hope for. Any flow that "locks" a rate at invoice time without a hedge is really an indicative price.

What you can do is show an indicative USDC estimate on the invoice for your own planning, clearly labeled, and quote for real at payment. Banco de México publishes its FIX reference rate on banking days (Banxico), which works for that kind of estimate. It's a reference, not a price you can trade at.

For the full on-ramp mechanics by country, see how to on-ramp BRL, MXN, ARS, and COP, and for the virtual account route on USD receivables, how to accept bank transfers and settle in stablecoins.

What to do next

Create one Pix payin quote on a development instance with currency_type: "sender", cover_fees: false, and your typical invoice total, then read receiver_amount and expires_at. The payin quotes docs have the exact request body.

FAQ