Docs
API 2026-05-27.dahlia Dashboard

Payments

Send the buyer to a Fluveo page that takes the payment.

With hosted checkout you skip the payment form. Your server describes the cart in one call, and the answer is a Checkout Session: a page on Fluveo's domain with its own address. You send the buyer to that address. The page shows the cart and its total, collects the payment, and returns the buyer to your shop. No card number ever reaches your servers. Each session pays through a PaymentIntent, so Accept a payment holds for it too.

Pick it when a page you host is enough and you would rather not build a form. Checkout platforms can point their buyers at this page as well.

Create a session and redirect the buyer#

  1. Create the Checkout Session#

    Give each cart line a name, a unit price and a quantity. Prices are integers in the currency's smallest unit, so 2000 is $20.00. Add success_url, where the buyer lands after paying, and cancel_url, where the buyer goes on giving up. Fluveo writes the session's id in place of {CHECKOUT_SESSION_ID} in success_url.

    Create a Checkout Session
    curl https://api.fluveo.com/v1/checkout/sessions \
      -H "Authorization: Bearer sk_test_..." \
      -H "Idempotency-Key: cart-88-session" \
      -d mode=payment \
      -d "line_items[0][price_data][currency]=usd" \
      -d "line_items[0][price_data][unit_amount]=2000" \
      -d "line_items[0][price_data][product_data][name]=Blue T-shirt" \
      -d "line_items[0][quantity]=1" \
      --data-urlencode "success_url=https://shop.example.com/thanks?session={CHECKOUT_SESSION_ID}" \
      --data-urlencode "cancel_url=https://shop.example.com/cart" \
      -d client_reference_id=cart-88
    const response = await fetch("https://api.fluveo.com/v1/checkout/sessions", {
      method: "POST",
      headers: {
        Authorization: "Bearer sk_test_...",
        "Idempotency-Key": "cart-88-session",
      },
      body: new URLSearchParams({
        mode: "payment",
        "line_items[0][price_data][currency]": "usd",
        "line_items[0][price_data][unit_amount]": "2000",
        "line_items[0][price_data][product_data][name]": "Blue T-shirt",
        "line_items[0][quantity]": "1",
        success_url: "https://shop.example.com/thanks?session={CHECKOUT_SESSION_ID}",
        cancel_url: "https://shop.example.com/cart",
        client_reference_id: "cart-88",
      }),
    });
    const session = await response.json();
    console.log(session.url);
    import requests
    
    response = requests.post(
        "https://api.fluveo.com/v1/checkout/sessions",
        headers={
            "Authorization": "Bearer sk_test_...",
            "Idempotency-Key": "cart-88-session",
        },
        data={
            "mode": "payment",
            "line_items[0][price_data][currency]": "usd",
            "line_items[0][price_data][unit_amount]": 2000,
            "line_items[0][price_data][product_data][name]": "Blue T-shirt",
            "line_items[0][quantity]": 1,
            "success_url": "https://shop.example.com/thanks?session={CHECKOUT_SESSION_ID}",
            "cancel_url": "https://shop.example.com/cart",
            "client_reference_id": "cart-88",
        },
    )
    session = response.json()
    print(session["url"])
    <?php
    $ch = curl_init("https://api.fluveo.com/v1/checkout/sessions");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            "Authorization: Bearer sk_test_...",
            "Idempotency-Key: cart-88-session",
        ],
        CURLOPT_POSTFIELDS => http_build_query([
            "mode" => "payment",
            "line_items" => [[
                "price_data" => [
                    "currency" => "usd",
                    "unit_amount" => 2000,
                    "product_data" => ["name" => "Blue T-shirt"],
                ],
                "quantity" => 1,
            ]],
            "success_url" => "https://shop.example.com/thanks?session={CHECKOUT_SESSION_ID}",
            "cancel_url" => "https://shop.example.com/cart",
            "client_reference_id" => "cart-88",
        ]),
    ]);
    $session = json_decode(curl_exec($ch), true);
    echo $session["url"];
  2. Send the buyer to the url#

    The session's id starts with cs_test_. It is open and unpaid, and url is its page. Redirect the buyer's browser there. payment_intent is null until the buyer presses pay on the page; from then on it holds the pi_ id.

    Response (trimmed)
    {
      "id": "cs_test_...",
      "object": "checkout.session",
      "mode": "payment",
      "status": "open",
      "payment_status": "unpaid",
      "amount_total": 2000,
      "currency": "usd",
      "payment_intent": null,
      "url": "https://.../c/cs_test_...",
      "expires_at": 1767312000
    }
  3. Fulfil from the webhook#

    Ship the order when your endpoint gets checkout.session.completed, with payment_status paid. Fluveo sends payment_intent.succeeded beside it. Treat the visit to success_url as a thank-you page only. It proves nothing: the address can be typed by hand, and the buyer may never get back to it.

    checkout.session.completed (trimmed)
    {
      "id": "evt_...",
      "type": "checkout.session.completed",
      "data": {
        "object": {
          "id": "cs_test_...",
          "object": "checkout.session",
          "status": "complete",
          "payment_status": "paid",
          "client_reference_id": "cart-88"
        }
      }
    }
Parameters you can send
  • modestringRequired

    payment charges the cart. setup saves the buyer's card with a SetupIntent and charges nothing: see Save a card without charging.

  • line_itemsarrayOptional

    Required in payment mode, refused in setup mode. The cart, one entry per line: line_items[0][...], line_items[1][...]. At most 100 lines, every line in one currency.

    2 child fields
    • price_dataobjectRequired

      The price of one unit.

      3 child fields
      • currencystringRequired

        A currency your account accepts, lowercase, such as usd.

      • unit_amountintegerRequired

        One unit's price, as an integer in the currency's smallest unit.

      • product_dataobjectRequired
        1 child field
        • namestringRequired

          The line's name on the page.

    • quantityintegerOptional

      Units of this line. Defaults to 1.

  • success_urlstringOptional

    The buyer's next stop after paying. {CHECKOUT_SESSION_ID} becomes the session's id.

  • cancel_urlstringOptional

    The buyer's next stop on leaving the page without paying.

  • client_reference_idstringOptional

    Your id for this cart or order, returned on the session and its events.

  • customer_emailstringOptional

    Fills the email box for the buyer.

  • customerstringOptional

    A cus_ id of your store. Send this or customer_email, not both.

  • expires_atintegerOptional

    Unix time at which the page stops taking payment, 30 minutes from now or later. Without it, the page lasts 24 hours.

  • metadataobjectOptional

    Key-value pairs of yours, kept on the session.

  • payment_intent_dataobjectOptional

    Settings for the session's PaymentIntent, read as POST /v1/payment_intents reads them: capture_method, description, metadata, receipt_email, setup_future_usage, shipping, statement_descriptor and statement_descriptor_suffix. A setup session refuses it.

  • setup_intent_dataobjectOptional

    description and metadata for the SetupIntent of a setup session. A payment session refuses it.

  • What the page lets the buyer pay with. Without it, the page offers the methods your account has on in its payment method configuration. See Payment methods on the page.

  • payment_method_options[pix][expires_after_seconds] (60 to 86400) and payment_method_options[boleto][expires_after_days] (0 to 60). Send each one with its method in payment_method_types.

Payment methods on the page#

payment_method_types sets what the page shows. Without it, the page shows every method your account accepts and has on in its payment method configuration, less the ones the session cannot take: payment_intent_data[capture_method]=manual leaves out pix and boleto, and payment_intent_data[setup_future_usage]=on_session leaves out pix. The session call and its fields are the same whatever the method.

payment_method_typesOn the pageOptions
cardA card formnone
pixA Pix QR code and a code to copypayment_method_options[pix][expires_after_seconds]
boletoA Boleto with its line and PDFpayment_method_options[boleto][expires_after_days]

Send several methods together and the buyer picks one. Each method charges in the currencies listed in Payment methods, and a method your account does not accept answers a 400.

A total under the currency's minimum charge, such as $0.50 (50) in usd, answers a 400. So does a parameter the session does not take, and the error names it. Every route is in the API reference.

Save a card without charging#

A session with mode=setup charges nothing. Its page asks for the buyer's card and saves it with a SetupIntent, so you can charge that card later with a PaymentIntent. Send no line_items: a setup session refuses them. Send currency (or payment_method_types), and a customer to attach the saved card to.

Create a setup session
curl https://api.fluveo.com/v1/checkout/sessions \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: cus-42-save-card" \
  -d mode=setup \
  -d currency=usd \
  -d customer=cus_... \
  --data-urlencode "success_url=https://shop.example.com/card-saved?session={CHECKOUT_SESSION_ID}" \
  --data-urlencode "cancel_url=https://shop.example.com/account"

The session names its SetupIntent in setup_intent (a seti_ id) from the start. payment_intent and amount_total are null, and payment_status stays no_payment_required for the whole life of the session. Without payment_method_types, the page offers the methods your account has on in its payment method configuration that a session can save; a Pix is never saved.

statuspayment_statusMeaning
openno_payment_requiredThe page waits for the buyer's card.
completeno_payment_requiredThe card is saved: the SetupIntent is succeeded and its payment_method is attached to the Customer.
expiredno_payment_requiredClosed with nothing saved. The SetupIntent is canceled.

When the buyer saves the card, your endpoint gets setup_intent.succeeded and checkout.session.completed, with payment_status no_payment_required. Read the saved pm_ from the SetupIntent's payment_method with GET /v1/setup_intents/{id}.

Session statuses#

statuspayment_statusMeaning
openunpaidThe page waits for the buyer: nothing paid yet, a card attempt running, or a Pix QR not yet paid.
completepaidPaid. The PaymentIntent is succeeded.
completeunpaidThe card is authorized and waits for your capture (payment_intent_data[capture_method]=manual), or a Boleto is issued and not yet paid.
expiredunpaidClosed unpaid, by expires_at or by you. Its PaymentIntent is canceled.

Other calls#

  • GET /v1/checkout/sessions/{id} returns one session. Your success_url page can call it to pick its message.
  • GET /v1/checkout/sessions returns sessions, newest first. Narrow it with status (open, complete, expired), payment_intent, customer or created.
  • GET /v1/checkout/sessions/{id}/line_items returns the cart lines.
  • POST /v1/checkout/sessions/{id} updates metadata and nothing else.
  • POST /v1/checkout/sessions/{id}/expire closes an open session now, say after the cart changed. Its PaymentIntent is canceled and checkout.session.expired goes out. A paid session refuses it.
Expire a session
curl -X POST https://api.fluveo.com/v1/checkout/sessions/cs_test_.../expire \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: cart-88-expire"

Events#

EventWhen
checkout.session.completedThe buyer paid, or a setup session saved the card. With manual capture, the card was authorized; with a Boleto, it was issued: payment_status is then unpaid. A Pix sends it once paid.
checkout.session.async_payment_succeededAn issued Boleto was paid.
checkout.session.async_payment_failedAn issued Boleto expired unpaid.
checkout.session.expiredThe session closed unpaid, at expires_at or by your call.

The session's PaymentIntent sends its own events too. They are listed on Accept a payment.

Test it#

  1. Create a session with a sk_test_ key, as above, and open its url in a browser.
  2. Pay with 4000 0000 0000 0002, any future expiry and any CVC. The page shows the decline and stays open.
  3. Pay with 4242 4242 4242 4242. The browser goes to your success_url.
  4. GET the session. status is complete, payment_status is paid, and your endpoint got checkout.session.completed.
  5. Create another session and expire it. status is expired, and checkout.session.expired arrives.
  6. Repeat the create with the same Idempotency-Key and body. The same cs_test_ comes back, with Idempotent-Replayed: true.

Next#