---
title: "fluveo.js errors"
description: "Each integration error, and its fix."
url: https://docs.fluveo.com/payments/fluveo-js-errors/
---

> 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/fluveo-js-errors.curl.md (cURL), https://docs.fluveo.com/payments/fluveo-js-errors.node.md (Node.js), https://docs.fluveo.com/payments/fluveo-js-errors.python.md (Python), https://docs.fluveo.com/payments/fluveo-js-errors.php.md (PHP).

# fluveo.js errors

Each integration error, and its fix.

[fluveo.js](https://docs.fluveo.com/payments/fluveo-js.md) reports two kinds of error, and they never mix.

- A **payment error** is about the buyer's payment, such as a declined card. `confirmPayment()` resolves with it as `{ error }`: the API's own error object, with `type`, `code`, `decline_code`, `message` and `doc_url`. When `type` is `card_error` or `validation_error`, `message` is written for the buyer. [Errors](https://docs.fluveo.com/errors.md) lists the codes.
- An **integration error** is a mistake in the code that calls `fluveo.js`. The script throws it at the call, and the buyer never sees it. This page lists every one.

An integration error is an `Error` whose `name` is `FluveoIntegrationError`. It has a stable `code` and a `doc_url`, a link that opens this page at the section with the same name. Its `message` always has the same form: what is wrong, how to fix it, then the `doc_url`.

*An integration error in the browser console*

```text
FluveoIntegrationError: elements() needs { clientSecret }, or { mode: "payment", amount, currency }. Pass the client_secret of a PaymentIntent your server made (POST /v1/payment_intents), or the amount and currency of the payment your server will make when the buyer presses Pay. https://docs.fluveo.com/payments/fluveo-js-errors/#client_secret_missing
```

> **For AI agents:**
>
> When fluveo.js throws, read error.code and fix the call as the section of that code says. Do not catch the error to hide it, and do not show its message to the buyer. A call or an option that throws option_not_supported, element_not_supported or not_available_yet does not exist in fluveo.js: use what the message names, never a call you remember from another payment library.

## secret_key_in_browser

`Fluveo()` got a secret key (`sk_...`), a restricted key (`rk_...`) or a webhook secret (`whsec_...`). Those stay on your server. The message names only the kind of key, never the key.

Fix: pass your publishable key (`pk_test_...`). The secret reached a browser, so treat it as leaked: rotate it in the Dashboard under **Settings**, **API keys**, with the expiration **Now**. See [Authentication](https://docs.fluveo.com/start/authentication.md).

## publishable_key_missing

`Fluveo()` got no key, an empty one, or the text `undefined` or `null`.

Fix: pass your publishable key, `Fluveo("pk_test_...")`, and check that your server has it to write.

## not_a_fluveo_key

The first argument of `Fluveo()` is not a publishable key. A publishable key is `pk_test_` or `pk_live_` followed by 43 characters. Usually it was cut short or is another service's key.

Fix: copy the publishable key from the Dashboard under **Settings**, **API keys**, and check that your server or your build writes it into the page.

## client_secret_missing

`fluveo.elements()` was called with neither `clientSecret` nor `mode`, or with a `clientSecret` that is empty. This also happens when the request to your server failed and the page passed on `undefined`.

Fix: create a PaymentIntent on your server with `POST /v1/payment_intents`, send its `client_secret` to the page and pass it as `fluveo.elements({ clientSecret })`. Or mount the form before the PaymentIntent exists, with `fluveo.elements({ mode: "payment", amount, currency })`.

## option_not_supported

A call got an option, an event name or a value that `fluveo.js` does not have. The message names the call as you wrote it and lists what the call takes.

Fix: keep only what the message lists. [fluveo.js](https://docs.fluveo.com/payments/fluveo-js.md) shows every call with its options, and [Form options](https://docs.fluveo.com/payments/fluveo-js.md#form-options) what each value of an option does.

| Call | What it takes |
| --- | --- |
| `Fluveo(publishableKey, options)` | `locale` |
| `fluveo.elements(options)` | `clientSecret`, or `mode`, `amount` and `currency`; `appearance`, `fonts`, `locale`, `loader` |
| `elements.create("payment", options)` | `layout`, `fields`, `defaultValues` |
| `elements.update(options)` | `appearance`, `fonts`, `locale`, `loader`, and on elements made with `mode` also `amount` and `currency` |
| `element.update(options)` | `layout`, `fields`, `defaultValues` |
| `element.on(event, handler)` | `"ready"`, `"change"`, `"focus"`, `"blur"`, `"loaderror"` |
| `fluveo.confirmPayment(options)` | `elements`, `clientSecret` (only with `mode`), `confirmParams`, `redirect` |
| `confirmParams` | `return_url`, `payment_method_data` with `billing_details` only |
| `redirect` | `"always"` or `"if_required"` |
| `appearance` | `theme`, `variables`, `labels`, `inputs`, `disableAnimations` |
| `appearance.theme` | `"fluveo"`, `"night"`, `"flat"` |
| `appearance.labels` | `"above"`, `"floating"`, `"auto"` |
| `appearance.inputs` | `"spaced"`, `"condensed"` |
| `appearance.disableAnimations` | `true` or `false` |
| `appearance.variables` | `colorPrimary`, `colorBackground`, `colorText`, `colorTextSecondary`, `colorTextPlaceholder`, `colorDanger`, `fontFamily`, `fontSizeBase`, `spacingUnit`, `borderRadius` |
| `locale` | `"auto"`, `"en"`, `"pt-BR"` |
| `loader` | `"auto"`, `"always"`, `"never"` |
| `layout` | `"tabs"`, `"accordion"`, `"auto"`, or an object with `type`, `defaultCollapsed`, `radios`, `spacedAccordionItems` |
| `layout.radios` | `"always"`, `"never"`, `"if_multiple"`, `"auto"`, `true` or `false` |
| `layout.defaultCollapsed`, `layout.spacedAccordionItems` | `true` or `false` |
| `fields` | `billingDetails`, which is `"auto"`, `"never"` or an object with `address`; `address` is `"auto"`, `"never"` or an object with `country` and `postalCode`, each `"auto"` or `"never"` |
| `defaultValues` | `billingDetails.address.country`, the two-letter code of a country the form offers, and `billingDetails.address.postal_code`, a text |

The message lists every name the call knows. Of those names, `fonts` throws [not_available_yet](https://docs.fluveo.com/payments/fluveo-js-errors.md#not_available_yet), and so do `locale` in `elements.update()` and `fields` and `defaultValues` in `element.update()`.

## script_not_from_fluveo

The script that is running was not loaded from `https://elements.fluveo.com/v1/`. It was copied into a bundle, served from another host, or loaded from `/v1` without the last slash.

Fix: put `<script src="https://elements.fluveo.com/v1/"></script>` in the page, or add that same tag from your code. Never save a copy of the script: a copy misses every fix. A query string after `/v1/` is fine.

## insecure_page

A live key (`pk_live_...`) was used on a page that is not served over `https`.

Fix: serve the page over `https`. While you build on `http://localhost`, use your test key (`pk_test_...`).

## container_not_found

`element.mount(target)` found no element. The selector matches nothing, the element is not in the page when `mount()` runs, or `target` is neither a selector nor an element.

Fix: call `mount()` after the container exists, with its selector (`"#payment-element"`) or the element itself. In a component, mount in the effect that runs after the first render.

## element_not_supported

`elements.create()` got a type other than `"payment"`, such as `"card"` or `"cardNumber"`. The payment element is the only element: it holds the card number, the expiration date and the security code in one form.

Fix: use `elements.create("payment")`.

## client_secret_wrong_account

The form asked the API for the PaymentIntent, and the API does not know this client secret for this publishable key. The two belong to different accounts or different modes, or the client secret is not one your server got from the API. The form cannot load, so this error does not come from a call: the element's `loaderror` event carries it, and the console prints it.

Fix: use the `pk_...` and the `sk_...` of the same account, both test or both live, and pass the `client_secret` exactly as `POST /v1/payment_intents` answered it.

## client_secret_invalid

`fluveo.retrievePaymentIntent()` got something that is not the client secret of a PaymentIntent. The usual cause is a return page opened without its query, where `payment_intent_client_secret` reads as `null`.

Fix: pass the `payment_intent_client_secret` parameter of the return page's address, or the `client_secret` your server got. When the parameter is missing, send the buyer back to the checkout.

## elements_missing

`fluveo.confirmPayment()` got no `elements`, an `elements` object made by another `Fluveo()`, or one whose payment element is not mounted in the page.

Fix: pass the object that `fluveo.elements()` returned, after `elements.create("payment").mount("#payment-element")`, and confirm with the same `fluveo` that made it.

## return_url_missing

`fluveo.confirmPayment()` needs `confirmParams.return_url`, and it must be an absolute `http` or `https` address. A path such as `"/complete"` is refused.

Fix: pass `confirmParams: { return_url: location.origin + "/complete" }`. The buyer lands there when the payment is done, and after 3D Secure.

## confirm_client_secret_missing

`fluveo.confirmPayment()` got no `clientSecret`, or one that is not a PaymentIntent's client secret, for elements made with `{ mode, amount, currency }`.

Fix: at the press of Pay, call `elements.submit()`, create the PaymentIntent on your server and pass its `client_secret` as `confirmPayment({ elements, clientSecret, confirmParams })`.

## submit_not_mounted

`elements.submit()` ran while the payment element was not mounted in the page: before `mount()`, or after `unmount()` or `destroy()`.

Fix: call it after `elements.create("payment").mount("#payment-element")`, when the buyer presses Pay.

## submit_required

`fluveo.confirmPayment()` of elements made with `{ mode, amount, currency }` ran with no `elements.submit()` called before it. One call serves until a confirm ends in anything but a field's `validation_error`, so call it at each press of Pay.

Fix: in the Pay handler, run `const { error } = await elements.submit()` before any other async work, and stop on `error`. Then create the PaymentIntent and confirm. Let one press end before the next starts.

## currency_mismatch

`fluveo.confirmPayment()` of elements made with `{ mode, amount, currency }` got the client secret of a PaymentIntent in another currency than the form's. The form's currency is the one `fluveo.elements()` got, or the last one `elements.update({ currency })` set; `"USD"` and `"usd"` are the same currency. Nothing is charged: the form sends no card, and the PaymentIntent stays `requires_payment_method`. A PaymentIntent of another amount is no error: it is paid, at its own amount.

This error is not thrown. `confirmPayment()` resolves with it as `{ error }`: its `type` is `invalid_request_error`, its `code` is `currency_mismatch`, its `param` is `currency`, its `payment_intent` is the PaymentIntent, and its `message` names the PaymentIntent's currency. The form shows the buyer nothing for it. Like every confirm that ends in an error other than a field's, it uses the `elements.submit()` up.

Fix: create the PaymentIntent in the form's currency. When the buyer picks another currency on your page, call `elements.update({ currency })` first; at the next press, call `elements.submit()` and then `confirmPayment()`.

## not_available_yet

The call or the option is part of `fluveo.js`, and this build of the script does not act on it. It throws so that nothing is ignored in silence. Today these throw it: the option `fonts` of `fluveo.elements()`, `rules` inside `appearance`, and an option an `update()` cannot change yet, which is `fonts` or `locale` on `elements.update()` and `fields` or `defaultValues` on `element.update()`.

Fix: remove the option. The form works without it. Set the language with `fluveo.elements({ locale })`, and `fields` and `defaultValues` with `elements.create("payment")`: an `update()` changes none of the three. [fluveo.js](https://docs.fluveo.com/payments/fluveo-js.md#elements-update) lists what each `update()` takes.

## billing_details_missing

`fluveo.confirmPayment()` was called for a form that does not ask the buyer for the country or the postal code, and `confirmParams` has no value for it. `elements.create("payment", { fields })` hides a field with `"never"`, and the form sends nothing for a hidden field, so your page has to. The message names the option that hid the field and the value that is missing. Nothing is sent to the API.

| `fields` sets to `"never"` | `confirmParams` must hold |
| --- | --- |
| `fields.billingDetails.address.country` | `payment_method_data.billing_details.address.country` |
| `fields.billingDetails.address.postalCode` | `payment_method_data.billing_details.address.postal_code` |

`"never"` on `billingDetails` or on `address` hides both. A hidden field needs its value on every confirm, whatever the country, and a value that is empty or only spaces counts as missing.

Fix: pass what your page knows of the buyer, such as `confirmParams: { return_url, payment_method_data: { billing_details: { address: { country: "US", postal_code: "94107" } } } }`, or set the field to `"auto"` so the form asks for it.

## appearance_value_invalid

`fluveo.elements({ appearance })` or `elements.update({ appearance })` got a variable whose value is not of its kind. The message names the variable, the value it got, the kind it takes and an example. Each value is a text, written as CSS writes it: a number such as `4` is refused, and `"4px"` is taken.

| Variable of `appearance.variables` | Kind | What it takes |
| --- | --- | --- |
| `colorPrimary`, `colorBackground`, `colorText`, `colorTextSecondary`, `colorTextPlaceholder`, `colorDanger` | color | A hex color, a color name, or `rgb()`, `rgba()`, `hsl()`, `hsla()`, `hwb()`, `lab()`, `lch()`, `oklab()` or `oklch()` with no other function inside, such as `"#2961b2"`. Never `var()`, `url()`, `inherit`, `initial`, `unset`, `revert` or `currentColor`. |
| `fontFamily` | font family | A list of font names, up to 200 characters, such as `"Inter, sans-serif"`. |
| `fontSizeBase`, `spacingUnit`, `borderRadius` | length | `"0"`, or a number of up to three digits and two decimals in `px`, `rem` or `em`, such as `"4px"`. |

A name that `appearance` or `appearance.variables` does not have throws [option_not_supported](https://docs.fluveo.com/payments/fluveo-js-errors.md#option_not_supported), and `rules` throws [not_available_yet](https://docs.fluveo.com/payments/fluveo-js-errors.md#not_available_yet).

Fix: pass a value of the variable's kind, as the example in the message shows. Write a color of your stylesheet out in full: the form is a frame, and it cannot read a variable of your page.

## payment_intent_unexpected_state

The form asked the API for the PaymentIntent, and its `status` is `succeeded`, `processing`, `requires_capture` or `canceled`. Such a PaymentIntent takes no card, so the form shows no card field: the buyer reads one line that sends them to the store. The message names the status. This error does not come from a call: the element's `loaderror` event carries it, and the console prints it.

It happens when a buyer reloads the checkout, or comes back to it, after they paid: an `Idempotency-Key` built from the order's id gives your server the same PaymentIntent again.

Fix: read the PaymentIntent's `status` on your server before you send its `client_secret` to the page. For one of the four, show the buyer how the order stands in place of the form. In the page, hide your Pay button when `loaderror` carries this code.

A PaymentIntent that reaches one of these after the form mounted is the API's to refuse: `confirmPayment()` resolves with the API's own error of the same code, and the form shows no message for it.

## wrong_prop

A component of `@fluveo/react-fluveo-js` got a prop it does not take, or `<Elements>` got no `fluveo` prop. The message names the component and the prop.

| Component | Props it takes |
| --- | --- |
| `<Elements>` | `fluveo`, `options`, `children` |
| `<PaymentElement>` | `id`, `className`, `options`, `onReady`, `onChange`, `onFocus`, `onBlur`, `onLoadError` |

`<Elements>` also raises it when its `fluveo` prop is not what `loadFluveo()` gives. That one comes after the promise resolves, so the console prints it as a rejected promise, the page keeps rendering and the form never shows.

Fix: pass only the props of the table. `<Elements>` takes what `loadFluveo("pk_test_...")` returns as `fluveo`, the promise itself, and `{ clientSecret }` as `options`.

## elements_provider_missing

`<PaymentElement>`, `useFluveo()` or `useElements()` of `@fluveo/react-fluveo-js` ran in a component that is not inside `<Elements>`. The message names which of the three. Each reads the `Fluveo` object and the elements group from the `<Elements>` above it, so the component that renders `<Elements>` is itself outside it.

Fix: render the component that uses them inside `<Elements fluveo={fluveoPromise} options={{ clientSecret }}>`. Keep two components: one renders `<Elements>`, and one inside it renders `<PaymentElement />` and calls the two hooks.
