Docs
API 2026-05-27.dahlia Dashboard

Payments

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 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.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.
  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 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"]);
  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)
    {
      "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://..."
        }
      }
    }
  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.

  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).

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

The due date#

payment_method_options[boleto]
  • expires_after_daysintegerOptional

    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#

EventWhen
payment_intent.createdThe PaymentIntent was created.
payment_intent.requires_actionThe line and PDF are ready to show.
charge.succeeded, payment_intent.succeededThe bank confirmed the payment. Fulfil now.
charge.failed, payment_intent.payment_failedThe 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.comPaid 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 seeWhy
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_prohibitedThe 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#