> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.crisscross.money/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.crisscross.money/_mcp/server.

# Adaptive Currency Conversion

> Adaptive Currency Conversion (ACC) converts a checkout into the payer's currency, priced by a per-session rate (default) or a pre-fetched rate lock.

Adaptive Currency Conversion (ACC) lets you price in one currency (for example USD) and collect in another (for example KES). When a checkout session needs a conversion, the payer sees and pays the converted amount in their own currency, and the applied rate is snapshotted onto every transaction attempt for reconciliation.

There are **two ways to price the conversion**:

1. **Per-session rate** *(default)* — nothing to set. An implicit FX quote is minted at session creation, valid for minutes, and refreshed automatically on the hosted page if it expires.
2. **Rate lock** — pass a `rateLockId` at session creation to price the session with a rate you locked ahead of time: valid for hours and reusable across many sessions. See [Rate Locks](/rate-locks).

> **Note**
>
> ACC must be enabled on your account before you can use it, and that is something we switch on for
> you — there is no self-serve toggle. Until it is enabled, any session that would need a conversion
> is rejected with `403` and `error: "fx_not_enabled"`, whichever pricing method you use. [Get in touch](/support) to have it enabled for the currency pairs you need.

## Choosing between the two

Both surface an all-in rate at checkout — the difference is *when* the rate is committed and *how long* it holds.

|                     | Per-session rate (default)                       | Rate lock                              |
| ------------------- | ------------------------------------------------ | -------------------------------------- |
| Committed           | At session creation                              | Ahead of checkout (`POST /rate-locks`) |
| Valid for           | Minutes                                          | Hours                                  |
| Reusable            | Single session                                   | Many sessions until expiry             |
| Refreshed on expiry | Yes — automatically, in-place on the hosted page | No — fetch a new lock                  |
| Identifier          | None surfaced to the merchant                    | `rlk_`                                 |
| Applied via         | Implicit — no field to set                       | `rateLockId` on the session            |

Use a **rate lock** when you want one rate to hold across many checkouts (steady-state pricing). Use the **per-session rate** when you just need the payer's currency converted at the moment of purchase.

## When conversion kicks in

ACC activates when **all** of the following are true on session creation:

1. The session `currency` (the currency you price in) differs from the payer's `collectionCurrency`. If `collectionCurrency` is omitted, we default to the primary currency for `payerDetails.location`.
2. The merchant is provisioned for FX at checkout for the initiation→collection pair.

Which option prices the conversion depends on the request: with a `rateLockId` the session is priced at the lock's all-in rate (the lock's quote currency must match the collection currency); without one, the per-session rate is minted automatically. If no conversion is needed, the payer is simply charged in the initiation currency.

With a rate lock, the Create Checkout Session response returns the conversion as `fxCommit`. With the per-session rate, **the creation response does not return it** — you get back identifiers only; read the session back with [Get Checkout Session](/api-reference/collect/payments/payment-initiation/get-checkout-session) and inspect `fxCommit`. Either way the block is the same:

| Field                                     | Meaning                                                                   |
| ----------------------------------------- | ------------------------------------------------------------------------- |
| `collectionCurrency` / `collectionAmount` | What the payer is actually debited                                        |
| `allInRate`                               | The rate applied — mid plus markup                                        |
| `midRate` / `markupBps`                   | The breakdown behind it                                                   |
| `fee`                                     | The conversion fee, in the collection currency                            |
| `pricingModel`                            | `SPOT_QUOTE` for a per-session rate, `RATE_LOCK` for a lock               |
| `quoteId`                                 | The quote id, or the `rlk_` lock id                                       |
| `expiresAt`                               | When the commit lapses — minutes for a per-session rate, hours for a lock |

## What the payer sees

When a conversion is applied, the hosted checkout page shows the payer the original charge, the converted amount they will actually be debited, and the effective all-in rate — whichever option priced it. With the per-session rate, the rate refreshes on the page if the underlying quote expires mid-checkout, so the payer never sees a stale rate.

![](/_fern-files/_img/7d7b633b9ea34366f78da44a644e3bf91aea411824428c620f6156c1722547f0.webp)

### Merchant-controlled display

You can toggle each element of the buyer-facing FX breakdown via `paymentAttributes` on the session. All three toggles default to shown — set to `"false"` to hide.

| Attribute            | Default  | Purpose                                                                                                                    |
| -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `showChargedAs`      | `"true"` | Show the original merchant-priced amount (in the initiation currency) alongside the converted amount the payer is debited. |
| `showConversionRate` | `"true"` | Show the effective all-in rate the conversion was applied at.                                                              |

All `paymentAttributes` values are strings. To hide an element, pass `"false"`; anything else (or omitting the key) leaves it visible.

The all-in rate reflects the per-currency-pair markup configured on your account before you start transacting, so you control how much of the spread is passed on to your payers. Use these `paymentAttributes` to shape what the payer sees on the checkout page.

## The FX commit on the session

Whenever a conversion has priced a session, [Get a checkout session](/api-reference/collect/payments/payment-initiation/get-checkout-session) returns it inline as `fxCommit`:

```json
{
  "session": {
    "id": "01951c8a-7c3d-7e1f-9d4a-2b3c4d5e6f70",
    "amount": 10000,
    "currencyId": "USD",
    "fxCommit": {
      "quoteId": "qut_01951c8a-9d5e-7f3b-bc4f-6e7d8c9b0a21",
      "pricingModel": "SPOT_QUOTE",
      "collectionCurrency": "KES",
      "collectionAmount": "13260.00",
      "midRate": "130.00",
      "markupBps": 200,
      "allInRate": "132.60",
      "fee": "260.00",
      "expiresAt": "2026-06-24T10:35:00Z"
    }
  }
}
```

`fxCommit.pricingModel` tells you which option priced the conversion: `SPOT_QUOTE` for a per-session quote, or `RATE_LOCK` for a session priced by a [rate lock](/rate-locks) — in which case `fxCommit.quoteId` is the `rlk_` rate lock id.

`fxCommit` on the session is the *latest* committed conversion. The rate a payer actually transacted at is captured per-attempt on the [Transaction](/api-reference/collect/payments/manage-payments/get-transaction) — see the FX snapshot fields (`collection`, `fxMidRate`, `fxAllInRate`, `fxMarkupBps`). These are immutable once written and preserved for the life of the transaction.

## Per-session rate expiry — refresh on the page, reject on stale ingress

A per-session quote is valid for minutes, not hours. Two things happen when it approaches expiry:

* **On the hosted checkout page** — the page reads the new rate from the session, updates the displayed converted amount, and lets the payer proceed at the refreshed rate. No action needed by the merchant.
* **At payment ingress** — a payment submitted against an expired `fxCommit` is rejected with `422` (stale quote). The payer must refresh the page and re-submit against the current rate. This is a safety net for direct-API integrations and any race between rate refresh and payment submission.

Rate locks work differently: their expiry is hours, not minutes, and there's no in-place refresh — you fetch a new lock. See [Rate locks — validity and expiry](/rate-locks#validity-and-expiry).

## Reconciliation

Every transaction attempt carries an immutable snapshot of the rate it was actually charged at:

* `collection` — `{ amount, currency }` the payer was debited (major-unit decimal string + ISO 4217).
* `fxMidRate` — spot mid-market rate at the moment of commit.
* `fxAllInRate` — mid rate + markup — the rate the transaction was actually converted at.
* `fxMarkupBps` — markup in basis points (100 bps = 1%).

For reporting and reconciliation, the transaction's snapshot is authoritative. The session's `fxCommit` reflects the latest quote — which may have refreshed after a transaction was attempted. With a rate lock the rate never refreshes, so every transaction priced by the same lock reconciles at the same `fxAllInRate` — see [Rate locks — reconciliation](/rate-locks#reconciliation).

## What you don't need to do (per-session default)

* **You don't need to fetch a rate ahead of checkout** — the session mints the quote for you.
* **You don't need to refresh anything from the merchant side** — the hosted page and the ingress guard handle it.
* **You don't need to reconcile against an external quote** — the per-attempt snapshot on each transaction is the source of truth.

If you want an explicit, held rate instead — for example to guarantee the same price across a browsing session or across many payers — use a [rate lock](/rate-locks).