A Boleto is a bank slip (boleto bancário) that the buyer pays by typing its number or opening its PDF in a banking app or at a bank. It goes through the same PaymentIntent call as a card: only the method fields change. You create it with a due date up to 60 days out, the buyer pays before that date, and a webhook tells you once the bank confirms the money. The currency Boleto charges in is in the methods table.
Before you start#
- Your test secret key
sk_test_..., on an account with Boleto turned on. - A webhook endpoint that listens for
payment_intent.succeededandpayment_intent.payment_failed. - The payer's name, email, CPF or CNPJ, and full address in Brazil. You send them in the request, and the Boleto prints them.
Create and confirm the PaymentIntent#
Send the amount in the currency's smallest unit,
200(R$ 2,00) or more, the currency,boletoas the method, the payer, the days until the due date andconfirm=true. Give the order its ownIdempotency-Key, and send that same key again on a retry.Create a Boleto paymentcurl https://api.fluveo.com/v1/payment_intents \ -H "Authorization: Bearer sk_test_..." \ -H "Idempotency-Key: order-1043-boleto" \ -d amount=1500 \ -d currency=brl \ -d "payment_method_types[]=boleto" \ -d confirm=true \ -d "payment_method_data[type]=boleto" \ -d "payment_method_data[boleto][tax_id]=12345678909" \ -d "payment_method_data[billing_details][name]=Ada Pagadora" \ -d "payment_method_data[billing_details][email]=ada@example.com" \ -d "payment_method_data[billing_details][address][line1]=1000, Av. Paulista, Bela Vista" \ -d "payment_method_data[billing_details][address][city]=Sao Paulo" \ -d "payment_method_data[billing_details][address][state]=SP" \ -d "payment_method_data[billing_details][address][postal_code]=01310100" \ -d "payment_method_data[billing_details][address][country]=BR" \ -d "payment_method_options[boleto][expires_after_days]=3"const body = new URLSearchParams([ ["amount", "1500"], ["currency", "brl"], ["payment_method_types[]", "boleto"], ["confirm", "true"], ["payment_method_data[type]", "boleto"], ["payment_method_data[boleto][tax_id]", "12345678909"], ["payment_method_data[billing_details][name]", "Ada Pagadora"], ["payment_method_data[billing_details][email]", "ada@example.com"], ["payment_method_data[billing_details][address][line1]", "1000, Av. Paulista, Bela Vista"], ["payment_method_data[billing_details][address][city]", "Sao Paulo"], ["payment_method_data[billing_details][address][state]", "SP"], ["payment_method_data[billing_details][address][postal_code]", "01310100"], ["payment_method_data[billing_details][address][country]", "BR"], ["payment_method_options[boleto][expires_after_days]", "3"], ]); const response = await fetch("https://api.fluveo.com/v1/payment_intents", { method: "POST", headers: { Authorization: "Bearer sk_test_...", "Idempotency-Key": "order-1043-boleto", }, body, }); const paymentIntent = await response.json(); console.log(paymentIntent.next_action);import requests response = requests.post( "https://api.fluveo.com/v1/payment_intents", headers={ "Authorization": "Bearer sk_test_...", "Idempotency-Key": "order-1043-boleto", }, data={ "amount": 1500, "currency": "brl", "payment_method_types[]": "boleto", "confirm": "true", "payment_method_data[type]": "boleto", "payment_method_data[boleto][tax_id]": "12345678909", "payment_method_data[billing_details][name]": "Ada Pagadora", "payment_method_data[billing_details][email]": "ada@example.com", "payment_method_data[billing_details][address][line1]": "1000, Av. Paulista, Bela Vista", "payment_method_data[billing_details][address][city]": "Sao Paulo", "payment_method_data[billing_details][address][state]": "SP", "payment_method_data[billing_details][address][postal_code]": "01310100", "payment_method_data[billing_details][address][country]": "BR", "payment_method_options[boleto][expires_after_days]": 3, }, ) print(response.json()["next_action"])<?php $fields = [ "amount" => 1500, "currency" => "brl", "payment_method_types[]" => "boleto", "confirm" => "true", "payment_method_data[type]" => "boleto", "payment_method_data[boleto][tax_id]" => "12345678909", "payment_method_data[billing_details][name]" => "Ada Pagadora", "payment_method_data[billing_details][email]" => "ada@example.com", "payment_method_data[billing_details][address][line1]" => "1000, Av. Paulista, Bela Vista", "payment_method_data[billing_details][address][city]" => "Sao Paulo", "payment_method_data[billing_details][address][state]" => "SP", "payment_method_data[billing_details][address][postal_code]" => "01310100", "payment_method_data[billing_details][address][country]" => "BR", "payment_method_options[boleto][expires_after_days]" => 3, ]; $ch = curl_init("https://api.fluveo.com/v1/payment_intents"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer sk_test_...", "Idempotency-Key: order-1043-boleto", ], CURLOPT_POSTFIELDS => http_build_query($fields), ]); $paymentIntent = json_decode(curl_exec($ch), true); print_r($paymentIntent["next_action"]);Read the Boleto from next_action#
The PaymentIntent comes back in
requires_action, andnext_action.typeisboleto_display_details. Store the PaymentIntent id on your order.Now and then the line is not ready when the call returns. The answer is then still 200, with
status: "processing"andnext_action: null. Do not create another Boleto for the order. Fluveo finishes this one on its own: once the line is ready, the PaymentIntent moves torequires_actionand you getpayment_intent.requires_action. Wait for that event, or read the PaymentIntent again, and then show the Boleto. A retry with the sameIdempotency-Keygets the same answer.Response (trimmed){ "id": "pi_...", "object": "payment_intent", "amount": 1500, "currency": "brl", "status": "requires_action", "next_action": { "type": "boleto_display_details", "boleto_display_details": { "number": "23790000000000000000000000000000000000000000000", "expires_at": 1767322799, "hosted_voucher_url": "https://...", "pdf": "https://..." } } }Show it to the buyer#
Give the buyer two ways to pay:
number, the 47-digit typeable line (linha digitável), next to a button that copies it, and thepdfto download or print. To send the whole Boleto by email or chat, usehosted_voucher_url, a page that shows it. Print the due date fromexpires_at.Fulfil on the webhook#
Once the bank confirms the money, the PaymentIntent moves to
succeededand Fluveo sendscharge.succeededandpayment_intent.succeeded. Ship onpayment_intent.succeeded, once per PaymentIntent id, and only after itsFluveo-Signatureheader checks out (Webhook signatures).
The payer#
Send the payer in payment_method_data on the request that confirms the Boleto. A Customer on the PaymentIntent never stands in for the payer, whatever name, address or tax id it holds. When a request sends no payer, the PaymentIntent keeps the payer an earlier request on it gave; with none, the answer is 400 on payment_method_data[boleto].
The payer's CPF (11 digits) or CNPJ (14 characters). Dots, dashes and slashes are ignored. A CNPJ's first twelve characters may be letters or digits, in either case; its last two are digits.
The payer's name, printed on the Boleto.
The payer's email.
The payer's phone.
The payer's address in Brazil.
6 child fields
The street line, as free text:
Av. Paulista 1000. Written as number, street and district separated by commas (1000, Av. Paulista, Bela Vista), the Boleto prints the district apart; otherwise it printsline2, or the city, as the district.A complement, such as
Apto 12.The city.
The two-letter code of a Brazilian state, such as
SPorRJ.The CEP: 8 digits, or
01310-100with one dash after the fifth digit. Spaces and other separators are refused.Always
BR.
The due date#
Days from today until the due date, from 0 to 60, counted in São Paulo time.
0makes the Boleto due today. Without it, the Boleto is due in 3 days, and the PaymentIntent showspayment_method_options.boleto.expires_after_days3.
expires_at records the end of the nominal due date in São Paulo time (23:59:59, UTC-3), as a Unix timestamp. It is not the moment the PaymentIntent fails. Fluveo allows a banking-calendar clearing period, including weekend and holiday adjustments, before treating an unpaid Boleto as overdue.
When nobody pays: expiry#
After the banking-calendar clearing period, Fluveo checks the bank and cancels an overdue, unpaid Boleto. Once that cancellation is confirmed:
- the PaymentIntent goes back to
requires_payment_methodwithlast_payment_error.codepayment_intent_payment_attempt_expired; - you get
charge.failedandpayment_intent.payment_failed; - the Boleto is cancelled at the bank, and a late payment of it is refused there.
To try again, create a new PaymentIntent with a new Idempotency-Key.
Switch to another method#
A Boleto that waits for payment cannot be replaced: a confirm with another payment method, or an update of payment_method, answers 400 and the PaymentIntent stays requires_action with its Boleto. Wait until the buyer pays it or it expires.
Events of a Boleto#
| Event | When |
|---|---|
payment_intent.created | The PaymentIntent was created. |
payment_intent.requires_action | The line and PDF are ready to show. |
charge.succeeded, payment_intent.succeeded | The bank confirmed the payment. Fulfil now. |
charge.failed, payment_intent.payment_failed | The unpaid Boleto expired after the clearing period and bank cancellation. |
A Boleto never sends payment_intent.processing, not even when the create answers processing before its line is ready. Once the line is ready, it waits in requires_action until the bank confirms. A repeated bank notice sends no second event.
Test it#
Use a test key and the request above. Its values are test values: CPF 12345678909, the address on Avenida Paulista, CEP 01310100. No real Boleto is registered.
What you can test today with only your key:
- Create. The answer is
requires_actionwith a 47-digitboleto_display_details.number. - Validation. Try
amount=199(400 onamount), noline1(400 parameter_missing onbilling_details[address][line1]),state=XX, a 7-digit CEP orcountry=US: each answers 400 and names what is wrong.
Paying a test Boleto. Fluveo's test bank pays a test Boleto through the same path as a real one: the PaymentIntent becomes succeeded, you get charge.succeeded and payment_intent.succeeded, then charge.updated, and the sale shows in your balance and balance transactions. The payer email decides when:
billing_details[email] | In test mode |
|---|---|
succeed_immediately@example.com (any address whose part before the @ ends in succeed_immediately) | Paid within seconds of the confirm. |
fill_never@example.com, expire_immediately@example.com, expire_with_delay@example.com (the same rule) | Never paid; it waits until it expires. |
Any other address, such as ada@example.com | Paid about 3 minutes after the confirm. |
To pay a waiting Boleto yourself, open next_action.boleto_display_details.hosted_voucher_url, follow simular o pagamento and press Autorizar pagamento de teste. A paid, canceled or expired Boleto is never paid again, and nothing is simulated with a live key. A Boleto that is never paid expires after the banking-calendar clearing period and bank cancellation, not at the end of its due date. See Testing.
Common errors#
| What you see | Why |
|---|---|
400 amount_too_small on amount, "Amount must be no less than R$ 2.00 brl" | The amount is under R$ 2,00. |
400 parameter_missing on billing_details[address][line1] (or [city], [state], [country], [postal_code]) | That address field is missing or blank. |
400 on payment_method_data[boleto] | The tax id is missing. |
400 tax_id_invalid, "The provided Brazilian tax id does not appear to be valid. Please provide a valid CPF or CNPJ." | The CPF or CNPJ has wrong check digits. |
400 tax_id_prohibited | The tax id was sent in billing_details[tax_id] instead of boleto[tax_id]. |
400 on payment_method_options[boleto][expires_after_days] | The value is negative or over 60. |
400 on payment_method_types, "Boleto is not available for this payment." | No due date was sent and the account has no default, or Boleto is not turned on for the account. |
400 on currency, "The currency provided (...) is invalid. Payments with boleto support the following currencies: ..." | The currency is not one Boleto charges in. |
400, "capture_method=manual is not supported by payment method type boleto." | A Boleto is captured when it is paid: leave capture_method out. |
Next#
- Pix, which is paid in seconds and can be refunded.
- Refund a payment.
- Balance and payouts.
- The PaymentIntents reference.