---
title: "Boleto"
description: "Issue a Boleto, show its line and PDF, and learn when it is paid or expires. No refunds."
url: https://docs.fluveo.com/payments/boleto/
---

> Index of every page: https://docs.fluveo.com/llms.txt. Integrating Fluveo? Read https://docs.fluveo.com/agents/integrate.md first.

> Too long? This page with code in one language only: https://docs.fluveo.com/payments/boleto.curl.md (cURL), https://docs.fluveo.com/payments/boleto.node.md (Node.js), https://docs.fluveo.com/payments/boleto.python.md (Python), https://docs.fluveo.com/payments/boleto.php.md (PHP).

# Boleto

Issue a Boleto, show its line and PDF, and learn when it is paid or expires. No refunds.

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](https://docs.fluveo.com/payments/accept-a-payment.md) 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](https://docs.fluveo.com/payments.md#payment-methods).

> **Boleto has no refunds:** Fluveo gives no money back on a paid Boleto. `POST /v1/refunds` for one answers 400 "Refunds are not supported on this payment method." If the buyer returns the order, pay them back outside Fluveo.

## Before you start

- Your test secret key `sk_test_...`, on an account with Boleto turned on.
- A webhook endpoint that listens for `payment_intent.succeeded` and `payment_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.

### Step 1: Create and confirm the PaymentIntent

Send the amount in the currency's smallest unit, `200` (R$ 2,00) or more, the currency, `boleto` as the method, the payer, the days until the due date and `confirm=true`. Give the order its own `Idempotency-Key`, and send that same key again on a retry.

**Create a Boleto payment**

cURL:

```bash
curl 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"
```

Node.js:

```javascript
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);
```

Python:

```python
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:

```php
<?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"]);
```

### Step 2: Read the Boleto from next_action

The PaymentIntent comes back in `requires_action`, and `next_action.type` is `boleto_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"` and `next_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 to `requires_action` and you get `payment_intent.requires_action`. Wait for that event, or read the PaymentIntent again, and then show the Boleto. A retry with the same `Idempotency-Key` gets the same answer.

*Response (trimmed)*

```json
{
  "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://..."
    }
  }
}
```

### Step 3: 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 the `pdf` to download or print. To send the whole Boleto by email or chat, use `hosted_voucher_url`, a page that shows it. Print the due date from `expires_at`.

### Step 4: Fulfil on the webhook

Once the bank confirms the money, the PaymentIntent moves to `succeeded` and Fluveo sends `charge.succeeded` and `payment_intent.succeeded`. Ship on `payment_intent.succeeded`, once per PaymentIntent id, and only after its `Fluveo-Signature` header checks out ([Webhook signatures](https://docs.fluveo.com/webhooks/signatures.md)).

> **For AI agents:**
>
> For Boleto the tax id goes in payment_method_data[boleto][tax_id], never in payment_method_data[billing_details][tax_id] (that is refused). The payer comes only from payment_method_data: a customer=cus_... never stands in for it. A confirm can answer 200 with status "processing" and next_action null: do not create a new Boleto; wait for payment_intent.requires_action or GET the PaymentIntent until it shows next_action.boleto_display_details. address[line1] is free text, such as "Av. Paulista 1000"; written as three comma-separated parts, number, street, district ("1000, Av. Paulista, Bela Vista"), the district prints apart. postal_code is 8 digits, with or without one dash after the fifth, and no spaces. The minimum amount is 200 centavos. Without payment_method_options[boleto][expires_after_days] the Boleto is due in 3 days. Never offer a refund for a Boleto payment.

## 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]`.

**Payer**

- `payment_method_data[boleto][tax_id]` (string, required): 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.
- `payment_method_data[billing_details][name]` (string, required): The payer's name, printed on the Boleto.
- `payment_method_data[billing_details][email]` (string, required): The payer's email.
- `payment_method_data[billing_details][phone]` (string, optional): The payer's phone.
- `payment_method_data[billing_details][address]` (object, required): The payer's address in Brazil.
  - `line1` (string, required): 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 prints `line2`, or the city, as the district.
  - `line2` (string, optional): A complement, such as `Apto 12`.
  - `city` (string, required): The city.
  - `state` (string, required): The two-letter code of a Brazilian state, such as `SP` or `RJ`.
  - `postal_code` (string, required): The CEP: 8 digits, or `01310-100` with one dash after the fifth digit. Spaces and other separators are refused.
  - `country` (string, required): Always `BR`.

## The due date

**payment_method_options[boleto]**

- `expires_after_days` (integer, optional): Days from today until the due date, from 0 to 60, counted in São Paulo time. `0` makes the Boleto due today. Without it, the Boleto is due in 3 days, and the PaymentIntent shows `payment_method_options.boleto.expires_after_days` `3`.

`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_method` with `last_payment_error.code` `payment_intent_payment_attempt_expired`;
- you get `charge.failed` and `payment_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_action` with a 47-digit `boleto_display_details.number`.
- **Validation.** Try `amount=199` (400 on `amount`), no `line1` (400 parameter_missing on `billing_details[address][line1]`), `state=XX`, a 7-digit CEP or `country=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](https://docs.fluveo.com/start/testing.md).

## 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](https://docs.fluveo.com/payments/pix.md), which is paid in seconds and can be refunded.
- [Refund a payment](https://docs.fluveo.com/payments/refunds.md).
- [Balance and payouts](https://docs.fluveo.com/payments/payouts.md).
- [The PaymentIntents reference](https://docs.fluveo.com/api/payment_intents.md).
