Background
Historically when businesses expanded internationally, they would often show customers the same currency everywhere - usually US dollars, or sometimes euros. Any customers in countries with different currencies outside these regions would pay in USD/EUR, but then would be charged an additional conversion fee by their banks. This impacts conversion rates, with customers often unsure of exactly how much they'll be charged after these fees get added.
Platforms like Stripe make it easy to offer customers prices in their local currency, then have it converted back to your operating currency. The technical terms here are presentment currency (what the customer pays in) and settlement currency (what you ultimately receive). Stripe normally converts between these automatically using mid-market rates plus a small margin.
A challenge this introduces is knowing exactly how much money you're going to get back in your "home" currency. FX rates fluctuate all the time, so while you can estimate the conversion, chances are you'll consistently be facing accounting challenges for over/under estimating the amount which ultimately lands in your bank account.
Stripe's FX Quotes API is designed to remove that guessing.
What the FX Quotes API is
At its simplest, the API lets you request a quote for a conversion from one currency into another. The rates returned are guaranteed to hold for a given period of time. We can attach that quote to a PaymentIntent, Transfer, or supported object, and Stripe guarantees the conversion will happen at that rate for as long as the quote is active.
It's a deliberate shift from "we'll convert at whatever the rate happens to be at settlement" to "this is the rate we will use, full stop". That predictability is the main value proposition here.
Why this is helpful
Anyone working across currencies eventually discovers how slippery exchange rates can be. Small movements can create reconciliation noise, erode margins, or cause transfers to land slightly off the intended amount. Imagine you estimate a conversion price for a customer at €94.29 today, but the FX rate shifts before capture and they're billed €96.02. Or imagine a connected account expecting €20 but receiving €19.85 after settlement. These seem small individually but create support overhead and reconciliation friction at scale.
That's a sometimes-tolerable pain when you're converting your own revenue. It's far less practical when:
- you're trying to display a final price to a customer in their local currency, or
- you're operating a platform where connected accounts expect amounts to arrive precisely, not approximately.
The FX Quotes API gives you a predictable foundation. Instead of hoping the conversion works out, you know exactly how Stripe will handle it.
A note on directionality
One important detail which catches a lot of people out when first working with this API: currency conversion is not symmetrical. If a quote tells you that 2 GBP converts to 1 EUR, you cannot invert that and assume that 1 EUR gets you 2 GBP when going in the opposite direction. Real-world FX has buy rates and sell rates, and Stripe's API reflects that.
If you need both directions, you request two quotes. Or, more simply, always request the quote in the direction you intend to use.
Basic example: using FX Quotes with a PaymentIntent
A common job to be done when expanding internationally is to display a price in a customer's local currency, even if our Stripe account defaults to another one. Let's say we operate in GBP but want to price in Euro.
Step 1: request a quote
curl https://api.stripe.com/v1/fx_quotes \
-u "sk_test_123:" \
-H "Stripe-Version: 2025-08-27.basil; fx_quote_preview=v1" \
-d "to_currency"=gbp \
-d "from_currencies[]"=eur \
-d "lock_duration"=day
In payment scenarios, to_currency is typically the Stripe account's settlement currency, while from_currencies[] contain the currencies your customers pay in. After making the request, we receive a response that includes a locked conversion rate and the quote ID (fxq_xxxxxx).
{
"id": "fxq_xxx",
"object": "fx_quote",
"lock_duration": "day",
"lock_status": "active",
"rates": {
"eur": {
"exchange_rate": 0.853682,
"rate_details": {
"base_rate": 0.872885,
"duration_premium": 0.002,
"fx_fee_rate": 0.02
}
}
},
"to_currency": "gbp",
"usage": { "type": "payment" }
}
The exchange_rate parameter is inclusive of Stripe's fee for providing the FX, while base_rate is exclusive. If we want our customer to bear the full cost of the conversion, we divide our target price (£100) by exchange_rate (0.853682) and will charge €117.14. If we as a business absorb the cost of FX conversion, we divide by base_rate (0.872885), charging €114.56.
| Use case | Figure to use |
|---|---|
| Customer covers Stripe's FX fee | exchange_rate |
| Our business absorbs FX fee | base_rate |
Step 2: attach the quote to a PaymentIntent
curl https://api.stripe.com/v1/payment_intents \
-u "sk_test_123:" \
-H "Stripe-Version: 2025-08-27.basil; fx_quote_preview=v1" \
-d "amount"=11714 \
-d "currency"=eur \
-d "fx_quote"=fxq_xxxxxx
The PaymentIntent presented to the end user for €117.14 will, once paid, land as £100 in our Stripe account. We've removed any uncertainty as to what the customer will be charged, or what we'll receive.
Using FX Quotes with Stripe Connect
When working across currency borders with Stripe Connect, currency mismatches can become particularly painful. Platforms may operate in one currency while connected accounts operate in another, and small FX movements can create big operational noise.
There are two common FX scenarios when using Connect:
- Application fees: we charge the end customer in one currency, but the platform fee must be collected in another.
- Transfers between platform and connected accounts: we may need to send funds to a connected account in their local currency, even when our balance is in a different one. Precision matters here - creators, restaurants and merchants expect payouts to be exact.
The mechanics for application fees mirror what we did for PaymentIntents above, so we won't dwell on them here. The more interesting case is transfers, where the platform must move funds from its own balance to a connected account across currencies.
Example: guaranteeing a connected account receives exactly €20
Imagine we're a food-delivery platform running a 20% off promotion. A customer orders €100 of food, paying €80 to our platform. The €80 settles to our platform; the remaining €20 owed to the restaurant must be topped up from our platform's own balance. If our platform operates in GBP and the restaurant operates in EUR, we need to transfer exactly €20 from our GBP balance.
Request the quote (in the correct direction)
curl https://api.stripe.com/v1/fx_quotes \
-u "sk_test_123:" \
-H "Stripe-Version: 2025-08-27.basil; fx_quote_preview=v1" \
-d to_currency=eur \
-d "from_currencies[]"=gbp \
-d lock_duration=five_minutes \
-d "usage[type]"=transfer \
-d "usage[transfer][destination]"="{{CONNECTED_ACCOUNT_ID}}"
Because we want our connected account to be made whole, the platform absorbs the fee. We use base_rate in the calculation. €20 ÷ 1.14587 = £17.45.
Create the transfer using that quote
curl https://api.stripe.com/v1/transfers \
-u "sk_test_123:" \
-H "Stripe-Version: 2025-08-27.basil; fx_quote_preview=v1" \
-d amount=1745 \
-d currency=gbp \
-d destination="{{CONNECTED_ACCOUNT_ID}}" \
-d fx_quote=fxq_xxx
Stripe deducts £17.45 from the platform balance and guarantees the connected account receives exactly €20. No reconciliation surprises, no corrective payouts, no guesswork.
How long is the quote valid for?
By default the quote returns the current market rate. The lock_duration parameter controls how long the quote is valid for - 5 minutes, an hour, or a day. Longer lock times are available on request, subject to Stripe approval. Stripe charges a higher fee for reserving the quote - the duration_premium field includes this charge.
The quote may still expire within this time. An extended rate quote for payments has a 3.5% rate threshold, and one for transfers has a 1% rate threshold. If the rate moves outside the threshold, the quote is invalidated and lock_status changes to expired. The fx_quote.expired webhook fires when this happens.
Predictability beats guesswork
Currency conversion is one of those things that feels like it should be simple, yet quietly introduces inconsistencies everywhere it appears. Stripe's FX Quotes API brings a welcome level of predictability. Instead of treating FX as a moving target, you treat it as a known value: a quote with limits, an expiry, and a guaranteed outcome.
If you price in multiple currencies, settle across currencies, or run a platform where transfers have to be correct, the FX Quotes API gives you a structure that is far more reliable than hoping for the best at capture time.