Docs
API 2026-05-27.dahlia Dashboard

Payments

One call for card, Pix and Boleto; only the currency and the payment method fields change.

Each payment you take is one PaymentIntent (pi_...): it holds the amount, the method and a status that moves from your first call to the end. Every payment method goes through the same call, POST /v1/payment_intents: you send the amount, your store's currency and the payment method, with confirm=true. A card is usually decided in the answer; a Pix or a Boleto waits for the buyer, and a webhook tells you when it is paid. This page shows the one-call flow, the two-call flow, 3D Secure and manual capture.

What changes by payment method#

Everything else in the request stays the same: the key, the Idempotency-Key, amount, confirm=true, metadata.

CardPixBoleto
currencyusd or brlbrlbrl
payment_method_types[]cardpixboleto
How you name the methodpayment_method=pm_... (in tests, pm_card_visa)payment_method_data[type]=pix and the payer in payment_method_data[billing_details]payment_method_data[type]=boleto, the tax id in payment_method_data[boleto][tax_id], and the payer's name, email and address
Optionscapture_method, return_urlpayment_method_options[pix][expires_after_seconds]payment_method_options[boleto][expires_after_days]
The answersucceeded (or requires_action for 3D Secure)requires_action with a Pix coderequires_action with a Boleto line and PDF
DetailsCardsPixBoleto

Take a payment in one call#

  1. Create and confirm the PaymentIntent#

    Send the amount in the currency's smallest unit, the currency, the payment method and confirm=true. Use one Idempotency-Key per order, and send that same key again if you retry.

    For a card, name the PaymentMethod in payment_method; in tests, pm_card_visa is always approved. 2000 is $20.00.

    Card: $20.00
    curl https://api.fluveo.com/v1/payment_intents \
      -H "Authorization: Bearer sk_test_..." \
      -H "Idempotency-Key: order-1042-pay" \
      -d amount=2000 \
      -d currency=usd \
      -d "payment_method_types[]=card" \
      -d payment_method=pm_card_visa \
      -d confirm=true \
      -d "metadata[order_id]=1042"
    const response = await fetch("https://api.fluveo.com/v1/payment_intents", {
      method: "POST",
      headers: {
        Authorization: "Bearer sk_test_...",
        "Idempotency-Key": "order-1042-pay",
      },
      body: new URLSearchParams({
        amount: "2000",
        currency: "usd",
        "payment_method_types[]": "card",
        payment_method: "pm_card_visa",
        confirm: "true",
        "metadata[order_id]": "1042",
      }),
    });
    const paymentIntent = await response.json();
    console.log(response.status, paymentIntent.status);
    import requests
    
    response = requests.post(
        "https://api.fluveo.com/v1/payment_intents",
        headers={
            "Authorization": "Bearer sk_test_...",
            "Idempotency-Key": "order-1042-pay",
        },
        data={
            "amount": 2000,
            "currency": "usd",
            "payment_method_types[]": "card",
            "payment_method": "pm_card_visa",
            "confirm": "true",
            "metadata[order_id]": "1042",
        },
    )
    payment_intent = response.json()
    print(response.status_code, payment_intent["status"])
    <?php
    $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-1042-pay",
        ],
        CURLOPT_POSTFIELDS => http_build_query([
            "amount" => 2000,
            "currency" => "usd",
            "payment_method_types" => ["card"],
            "payment_method" => "pm_card_visa",
            "confirm" => "true",
            "metadata" => ["order_id" => "1042"],
        ]),
    ]);
    $paymentIntent = json_decode(curl_exec($ch), true);
    echo $paymentIntent["status"];

    For a Pix, describe the method and its payer in payment_method_data. 1000 is R$ 10,00.

    Pix in BRL: R$ 10,00
    curl https://api.fluveo.com/v1/payment_intents \
      -H "Authorization: Bearer sk_test_..." \
      -H "Idempotency-Key: order-1042-pay" \
      -d amount=1000 \
      -d currency=brl \
      -d "payment_method_types[]=pix" \
      -d "payment_method_data[type]=pix" \
      -d "payment_method_data[billing_details][name]=Ada Pagadora" \
      -d "payment_method_data[billing_details][email]=ada@example.com" \
      --data-urlencode "payment_method_data[billing_details][phone]=+5511999999999" \
      -d "payment_method_data[billing_details][tax_id]=12345678909" \
      -d confirm=true \
      -d "metadata[order_id]=1042"
    const response = await fetch("https://api.fluveo.com/v1/payment_intents", {
      method: "POST",
      headers: {
        Authorization: "Bearer sk_test_...",
        "Idempotency-Key": "order-1042-pay",
      },
      body: new URLSearchParams({
        amount: "1000",
        currency: "brl",
        "payment_method_types[]": "pix",
        "payment_method_data[type]": "pix",
        "payment_method_data[billing_details][name]": "Ada Pagadora",
        "payment_method_data[billing_details][email]": "ada@example.com",
        "payment_method_data[billing_details][phone]": "+5511999999999",
        "payment_method_data[billing_details][tax_id]": "12345678909",
        confirm: "true",
        "metadata[order_id]": "1042",
      }),
    });
    const paymentIntent = await response.json();
    console.log(response.status, paymentIntent.status, paymentIntent.next_action);
    import requests
    
    response = requests.post(
        "https://api.fluveo.com/v1/payment_intents",
        headers={
            "Authorization": "Bearer sk_test_...",
            "Idempotency-Key": "order-1042-pay",
        },
        data={
            "amount": 1000,
            "currency": "brl",
            "payment_method_types[]": "pix",
            "payment_method_data[type]": "pix",
            "payment_method_data[billing_details][name]": "Ada Pagadora",
            "payment_method_data[billing_details][email]": "ada@example.com",
            "payment_method_data[billing_details][phone]": "+5511999999999",
            "payment_method_data[billing_details][tax_id]": "12345678909",
            "confirm": "true",
            "metadata[order_id]": "1042",
        },
    )
    payment_intent = response.json()
    print(response.status_code, payment_intent["status"], payment_intent["next_action"])
    <?php
    $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-1042-pay",
        ],
        CURLOPT_POSTFIELDS => http_build_query([
            "amount" => 1000,
            "currency" => "brl",
            "payment_method_types" => ["pix"],
            "payment_method_data" => [
                "type" => "pix",
                "billing_details" => [
                    "name" => "Ada Pagadora",
                    "email" => "ada@example.com",
                    "phone" => "+5511999999999",
                    "tax_id" => "12345678909",
                ],
            ],
            "confirm" => "true",
            "metadata" => ["order_id" => "1042"],
        ]),
    ]);
    $paymentIntent = json_decode(curl_exec($ch), true);
    echo $paymentIntent["status"];

    A Boleto is the same call with the fields of Boleto.

  2. Read the result#

    A card that is approved answers 200 with status: "succeeded". The PaymentIntent's latest_charge is the ch_ of the charge, and amount_received is what the buyer paid.

    Card: response (trimmed)
    {
      "id": "pi_...",
      "object": "payment_intent",
      "amount": 2000,
      "amount_received": 2000,
      "currency": "usd",
      "status": "succeeded",
      "latest_charge": "ch_...",
      "payment_method": "pm_...",
      "capture_method": "automatic_async",
      "metadata": { "order_id": "1042" }
    }

    A Pix or a Boleto answers status: "requires_action", and next_action holds what you show the buyer: a pix_display_qr_code (see Pix) or boleto_display_details (see Boleto). It is never paid at create time. No Charge exists yet: latest_charge stays null until the buyer pays or the code expires, and the Charge shows up with charge.succeeded or charge.failed.

    Pix: response (trimmed)
    {
      "id": "pi_...",
      "object": "payment_intent",
      "amount": 1000,
      "currency": "brl",
      "status": "requires_action",
      "latest_charge": null,
      "next_action": {
        "type": "pix_display_qr_code",
        "pix_display_qr_code": { "data": "00020101021226...", "expires_at": 1767225600 }
      }
    }

    capture_method is automatic_async when you send none. It captures exactly like automatic: the money is taken as soon as the card is approved, so a succeeded PaymentIntent is already captured and needs no capture call. You may send capture_method=automatic instead and the payment behaves the same; the field then shows automatic. Only manual holds the money until you capture (see Authorize now, capture later).

  3. Fulfil the order from the webhook#

    Fluveo sends payment_intent.succeeded (with charge.succeeded) to your webhook endpoint when the money is in. Ship the order when that event arrives and its Fluveo-Signature checks out, once per PaymentIntent id. The answer to your POST is only a hint: a payment that needs 3D Secure, a Pix, a Boleto, or a card you capture later, finishes after the call returns.

    In test mode, Fluveo's test bank pays a Pix or a Boleto for you, through the same path as a real payment: you get the same charge.succeeded and payment_intent.succeeded, then charge.updated once the sale's balance transaction is booked, and the sale shows in your balance and your balance transactions as a real one does. The payer email in payment_method_data[billing_details][email] decides when:

    • an address whose part before the @ ends in succeed_immediately, such as succeed_immediately@example.com, is paid within seconds of the confirm;
    • an address ending in expire_immediately, expire_with_delay or fill_never before the @ is never paid: it waits and expires on its own deadline (a Pix's expiry, a Boleto's clearing period), so give a Pix a short expires_after_seconds to watch it expire;
    • any other address, such as ada@example.com, is paid about 3 minutes after the confirm, unless the Pix code expires first;
    • a Pix with no email is never paid on its own.

    You can also pay a waiting one yourself: open the Pix's hosted_instructions_url or the Boleto's hosted_voucher_url in a browser, follow simular o pagamento and press Autorizar pagamento de teste. A Pix or Boleto that is already paid, canceled or expired is never paid again. With a live key nothing is simulated: the email is just the payer's email, and the pages have no test link.

Take a payment in two calls#

Create the PaymentIntent first when you want its id before the buyer picks a card, for example to store it with the order. Then confirm it.

1. Create
curl https://api.fluveo.com/v1/payment_intents \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: order-1043-create" \
  -d amount=4500 \
  -d currency=usd \
  -d "payment_method_types[]=card"

The PaymentIntent starts in requires_payment_method. Confirm it with the card:

2. Confirm
curl https://api.fluveo.com/v1/payment_intents/pi_.../confirm \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: order-1043-confirm" \
  -d payment_method=pm_card_visa
Parameters you can send on create
  • amountintegerRequired

    The amount in the smallest unit of the currency: 2000 is $20.00 in usd, 1000 is R$ 10,00 in brl. A Boleto takes at least 200.

  • currencystringRequired

    A currency your account accepts, in lower case, such as the default_currency of GET /v1/account. Any other is a 400 on currency.

  • card, pix or boleto. A method in payment_method_data[type] must also be in this list. The currency each method charges in is on Payments.

  • payment_methodstringOptional

    The card to charge: a pm_ id, or a test card name like pm_card_visa.

  • payment_method_dataobjectOptional

    A Pix or a Boleto and its payer, used once for this payment. Do not send it together with payment_method, and never put card numbers in it. See Pix and Boleto.

  • Options of one method: payment_method_options[pix] or payment_method_options[boleto].

  • confirmbooleanOptional

    true confirms in the same call. Default false.

  • capture_methodstringOptional

    For a card: automatic_async (the default), automatic or manual. automatic_async and automatic both capture as soon as the card is approved; manual holds the money until you capture. See Authorize now, capture later.

  • confirmation_methodstringOptional

    automatic (the default) or manual. With manual, a card that passes 3D Secure does not charge on its own: the PaymentIntent stops in requires_confirmation and charges only when you call /confirm again.

  • return_urlstringOptional

    Where the buyer comes back after 3D Secure. Only with confirm=true. Required when you leave out payment_method_types, unless you send automatic_payment_methods[allow_redirects]=never or off_session=true. See 3D Secure.

  • customerstringOptional

    A cus_ of your store. For a Pix, the Customer's name and tax id (br_cpf or br_cnpj) name the payer when the request carries none. A Boleto never takes its payer from the Customer: send it in the request (see Boleto).

  • descriptionstringOptional

    Your own text about the payment.

  • receipt_emailstringOptional

    The buyer's email for the receipt.

  • Text added to what the buyer sees on their card statement.

  • metadataobjectOptional

    Your own key-value pairs, sent as metadata[key]=value. Keys that start with fluveo_ are refused.

The API reference lists every route of the PaymentIntent. A parameter Fluveo does not take answers a 400 that names it, so an unknown field never passes in silence.

Statuses#

StatusWhat it meansWhat you do
requires_payment_methodNo payment method yet, the last card was declined, or a Pix or Boleto expired unpaid.Confirm with a payment method, or create a new Pix or Boleto.
requires_confirmationA card is set but the payment is not confirmed. An update that sets another payment_method on a waiting Pix drops its code and lands here. With confirmation_method=manual, a card that passed 3D Secure waits here too, with no next_action, no Charge and no event.Call /confirm.
requires_actionThe buyer must act: authenticate the card (3D Secure), or pay the Pix or the Boleto.Send the buyer to next_action.redirect_to_url.url, or show the Pix code or the Boleto. A confirm with another card replaces a 3D Secure or Pix wait; a waiting Boleto refuses it with 400.
processingThe card network has not answered yet, or a Boleto's line is not ready yet.Wait for the webhook.
requires_captureThe card is authorized; money is held, not taken.Capture or cancel.
succeededThe money is taken.Fulfil the order.
canceledClosed for good; nothing will be charged on it.Nothing; create a new one to try again.

Events#

EventWhen
payment_intent.createdThe PaymentIntent was created.
payment_intent.requires_actionThe buyer must authenticate (3D Secure), or the Pix code or Boleto is ready to show.
payment_intent.processingA card payment is waiting on the card network. A Boleto never sends it.
charge.succeeded + payment_intent.succeededThe buyer paid and the money is in.
charge.succeeded + payment_intent.amount_capturable_updatedA manual-capture card payment is authorized.
charge.captured + payment_intent.succeededYou captured an authorized payment.
charge.failed + payment_intent.payment_failedThe card was declined, or a Pix or Boleto expired unpaid. The PaymentIntent goes back to requires_payment_method.
payment_intent.canceledThe PaymentIntent moved to canceled.

A Pix or a Boleto creates its events in this order: payment_intent.created, payment_intent.requires_action, then the success or failure events. Its charge appears only with them: while the buyer has not paid, latest_charge is null. Events can arrive more than once and out of order. Keep the evt_ ids you handled and skip a repeat; read the PaymentIntent again when the order matters. The full list is on Event types.

When a card is declined#

A declined card answers 402 with an error of type card_error. error.code says why (for example card_declined, expired_card, incorrect_cvc), error.decline_code gives the bank's reason when there is one, and error.payment_intent holds the PaymentIntent, now back in requires_payment_method.

402 Payment Required (trimmed)
{
  "error": {
    "type": "card_error",
    "code": "card_declined",
    "decline_code": "generic_decline",
    "message": "Your card was declined.",
    "payment_intent": { "id": "pi_...", "status": "requires_payment_method" }
  }
}

Ask the buyer for another card and confirm the same PaymentIntent again, with a new Idempotency-Key because it is a new attempt. See Errors for every code.

3D Secure#

Some banks ask the buyer to prove they own the card. Fluveo handles this with a redirect:

  1. Confirm with return_url, the page on your site where the buyer should land afterwards.
  2. If the bank asks for authentication, the answer has status: "requires_action" and a next_action of type redirect_to_url.
  3. Send the buyer's browser to next_action.redirect_to_url.url. It is a Fluveo page; the bank's check opens inside it, so the address bar stays on Fluveo. In test mode it opens a test page with Complete and Fail instead (see Testing).
  4. The buyer comes back to your return_url. Do not trust that visit alone: read the PaymentIntent, or wait for payment_intent.succeeded or payment_intent.payment_failed.
next_action
{
  "status": "requires_action",
  "next_action": {
    "type": "redirect_to_url",
    "redirect_to_url": {
      "url": "https://.../v1/card_actions/...",
      "return_url": "https://shop.example.com/orders/1042/return"
    }
  }
}

Authorize now, capture later#

Use capture_method=manual when you only charge once you ship. The card is authorized and the money is held; nothing is taken until you capture. This works for every card payment.

Authorize
curl https://api.fluveo.com/v1/payment_intents \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: order-1044-pay" \
  -d amount=5000 \
  -d currency=usd \
  -d "payment_method_types[]=card" \
  -d payment_method=pm_card_visa \
  -d capture_method=manual \
  -d confirm=true

The PaymentIntent is now requires_capture and amount_capturable is 5000. Capture all of it with no body, or a smaller amount with amount_to_capture; the rest is released to the buyer.

Capture $30.00 of $50.00
curl https://api.fluveo.com/v1/payment_intents/pi_.../capture \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: order-1044-capture" \
  -d amount_to_capture=3000
const response = await fetch("https://api.fluveo.com/v1/payment_intents/pi_.../capture", {
  method: "POST",
  headers: {
    Authorization: "Bearer sk_test_...",
    "Idempotency-Key": "order-1044-capture",
  },
  body: new URLSearchParams({ amount_to_capture: "3000" }),
});
console.log((await response.json()).status);
import requests

response = requests.post(
    "https://api.fluveo.com/v1/payment_intents/pi_.../capture",
    headers={
        "Authorization": "Bearer sk_test_...",
        "Idempotency-Key": "order-1044-capture",
    },
    data={"amount_to_capture": 3000},
)
print(response.json()["status"])
<?php
$ch = curl_init("https://api.fluveo.com/v1/payment_intents/pi_.../capture");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer sk_test_...",
        "Idempotency-Key: order-1044-capture",
    ],
    CURLOPT_POSTFIELDS => http_build_query(["amount_to_capture" => 3000]),
]);
echo json_decode(curl_exec($ch), true)["status"];

A capture makes the PaymentIntent succeeded and sends charge.captured and payment_intent.succeeded. One capture per PaymentIntent: final_capture and multiple captures are not available.

To drop an authorization instead, cancel it. cancellation_reason is optional and takes requested_by_customer, duplicate, fraudulent or abandoned.

Cancel
curl https://api.fluveo.com/v1/payment_intents/pi_.../cancel \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: order-1044-cancel" \
  -d cancellation_reason=requested_by_customer

Capturing or canceling a PaymentIntent in the wrong status answers 400 payment_intent_unexpected_state.

Test it#

Use these test card names with a sk_test_ key. Each use makes a new pm_ for your store. Every test value is on Testing.

PaymentMethodResult
pm_card_visaApproved (Visa ending 4242).
pm_card_mastercard, pm_card_amex, pm_card_visa_debitApproved.
pm_card_visa_chargeDeclined402 card_declined, decline_code generic_decline.
pm_card_visa_chargeDeclinedInsufficientFunds402 card_declined, decline_code insufficient_funds.
pm_card_chargeDeclinedExpiredCard402 expired_card.
pm_card_chargeDeclinedIncorrectCvc402 incorrect_cvc.
pm_card_chargeDeclinedProcessingError402 processing_error.
pm_card_authenticationRequiredAsks for 3D Secure: confirm it with a return_url.
  1. Create and confirm with pm_card_visa: the answer is succeeded, and your endpoint gets payment_intent.succeeded.
  2. Do it again with pm_card_visa_chargeDeclined: the answer is 402, then confirm the same PaymentIntent with pm_card_visa and a new key.
  3. Authorize with capture_method=manual, then capture amount_to_capture=1500: amount_received is 1500.
  4. Confirm pm_card_authenticationRequired with a return_url: the answer is requires_action. Open next_action.redirect_to_url.url in a browser and complete the test page.
  5. Send the first request again with the same Idempotency-Key: you get the same PaymentIntent back, with the header Idempotent-Replayed: true, and no second charge.
  6. Create a Pix with the payer email succeed_immediately@example.com: the answer is requires_action with a pix_display_qr_code, and a few seconds later the PaymentIntent is succeeded and your endpoint gets payment_intent.succeeded. See Pix.

Next#