WooCommerce Currency Converter API and Shopify Multi-Currency Rates
Where store platforms get FX, why daily tables leak margin on volatile pairs, and how to refresh WooCommerce and Shopify rates hourly.
A catalog priced in USD and shown in EUR is only as honest as the FX rate behind that conversion. WooCommerce currency switchers and Shopify multi-currency setups often still lean on a daily central-bank fix, a typed-in plugin table, or the rate a payment processor applies at capture. On a quiet session that is close enough. On a day when GBP/USD, JPY, or an emerging-market pair moves a full percent, the storefront and the payout stop matching.
That gap does not appear as a failed checkout. It appears as leaked margin, invoices that disagree with the bank deposit, and finance teams reconciling two numbers for one order. The durable fix is to treat FX as an operational feed: refresh it on a schedule, freeze it on the invoice, and keep charge currency distinct from settlement currency.
Key takeaways
- WooCommerce and Shopify do not source live interbank rates; plugins, Markets, and processors do.
- Daily or manual tables leak margin on volatile pairs between the morning fix and capture.
- An hourly cron or webhook keeps display rates aligned without hitting the API on every product loop.
- Charge currency belongs on the customer invoice; settlement currency belongs on the ERP payout line.
- Shopify Markets is convenient; a custom app is required when the store must own the feed.
Where store platforms get their rates
Neither WooCommerce nor Shopify is a market-data vendor. Each platform stores a base currency, then delegates conversion to a plugin, an app, a payment processor, or a daily file from a central bank. Knowing which layer owns the number is the first integration decision.
WooCommerce
WooCommerce core is single-currency. Multi-currency arrives through plugins such as currency switchers, WooCommerce Multilingual, and Aelia-style converters. Those plugins typically offer a manual rate table, a provider dropdown (often the ECB or a public FX API), and an update interval that defaults to daily. The European Central Bank publishes a euro-based reference around 16:00 CET on TARGET business days. That series is appropriate for accounting. It is a poor checkout price for USD/TRY or GBP/JPY between 09:00 and 15:00.
Shopify Markets and apps
Shopify Markets with Shopify Payments converts storefront prices using Shopify's own rates and applies Shopify's currency conversion fee at payment. Third-party currency apps rewrite prices in the theme or through the Admin API. In both cases the merchant does not see an interbank bid/ask; the storefront shows a converted number that may lag the market and may already include a markup.
Payment processors
Card acquirers, Stripe, and PayPal convert at their own treasury rates when the charge currency differs from the settlement currency. That rate is the one that hits the bank account. Display rates that ignore it create a silent spread between the cart and the payout.
| Source | Typical refresh | Best use |
|---|---|---|
| ECB / central bank | Once per business day | Accounting, not live checkout |
| Manual plugin table | When staff remember | Rare pairs with no feed |
| Processor / Shopify Payments | At capture or payout | Settlement only |
| FX rates API | Minutes to hourly | Storefront display and invoices |
Why daily and manual rates lose money on volatile pairs
A daily refresh assumes the pair will sit still. Majors such as EUR/USD often do, for hours. Crosses and high-beta quotes do not. Policy decisions, CPI prints, and elections routinely produce intra-day moves larger than a typical retail buffer on FX. A rate typed in on Monday morning can be economically wrong by Tuesday's first European session.
Three failure modes show up in order data. First, stale display and live capture: the customer sees yesterday's EUR price while the processor converts today's USD settlement, and the merchant absorbs the difference. Second, a weekend gap on USD/JPY or USD/ZAR opens against a table nobody updated. Third, plugins store a mid-market number while the acquirer pays on a bid plus fee, so even an accurate daily mid is not the money that settles.
Hourly is the practical floor for storefronts that sell into more than one volatile quote currency. Minute-level ticks are rarely needed for cart display; daily is rarely safe beyond EUR, USD, and CHF on calm weeks. A public board of all live rates makes it obvious which catalog currencies actually move.
Charge currency versus settlement currency on invoices
Charge currency is what the customer authorizes and what belongs on the customer-facing invoice. Settlement currency is what the acquirer deposits. They are often not the same, and mixing them is how FX profit on the storefront turns into a short payout.
Consider a US store that settles in USD and lists a EUR 89.00 price. The shopper pays in EUR. The processor converts to USD at capture or on payout. If the storefront used a stale 1.10 and the processor used 1.08, the USD that lands is short. If the storefront used 1.08 and the processor used 1.10, the customer overpaid relative to the conversion that actually occurred — a trust and dispute problem, not a win. Those 1.10 / 1.08 figures are illustrative of direction, not a live quote.
Invoice hygiene follows from that split. Print charge currency, amount, FX pair, rate, and timestamp on the customer invoice. Record the settlement amount and processor FX as a separate treasury line in the ERP. Do not revalue the customer invoice when the payout rate later changes; that is a treasury event, not a sales event. B2B WooCommerce PDF invoices should freeze the rate at order time so a later hourly refresh cannot rewrite history.
Hourly refresh on WooCommerce: cron, webhook, and PHP
The reliable pattern is a scheduled job that pulls a rates API, writes a pair map into wp_options (or the currency plugin's table), and lets formatting code read only that map. WP-Cron is acceptable if traffic is steady; a system cron hitting wp-cron.php is more reliable for quiet shops. After the option is stored, a filter on the switcher's rate callback should read the local map. Do not call the remote API from a product loop.
add_action('init', function () {
if (!wp_next_scheduled('liverates_hourly_fx')) {
wp_schedule_event(time(), 'hourly', 'liverates_hourly_fx');
}
});
add_action('liverates_hourly_fx', 'liverates_refresh_woo_rates');
function liverates_refresh_woo_rates() {
$key = defined('LIVERATES_API_KEY') ? LIVERATES_API_KEY : '';
// Replace the URL with the endpoint issued on the account dashboard.
$res = wp_remote_get('https://www.live-rates.com/api/rates', array(
'timeout' => 10,
'headers' => array(
'Authorization' => 'Bearer ' . $key,
'Accept' => 'application/json',
),
));
if (is_wp_error($res) || (int) wp_remote_retrieve_response_code($res) !== 200) {
error_log('Live-Rates WooCommerce refresh failed');
return;
}
$body = json_decode(wp_remote_retrieve_body($res), true);
if (empty($body['rates']) || !is_array($body['rates'])) {
return;
}
$map = array();
foreach ($body['rates'] as $pair => $quote) {
$code = strtoupper(str_replace('/', '', $pair));
$map[$code] = isset($quote['rate']) ? (float) $quote['rate'] : (float) $quote;
}
update_option('liverates_fx_map', $map, false);
update_option('liverates_fx_updated', time(), false);
$base = get_woocommerce_currency();
$woo = array();
foreach (array('EUR', 'GBP', 'JPY', 'CAD', 'AUD') as $ccy) {
$k = $base . $ccy;
if (isset($map[$k])) {
$woo[$ccy] = $map[$k];
}
}
update_option('woocommerce_multi_currency_rates', $woo, false);
do_action('liverates_woo_rates_updated', $woo, $map);
}
// Hook name varies by currency-switcher plugin; keep a local fallback.
add_filter('woocommerce_currency_switcher_rate', function ($rate, $currency) {
$map = get_option('liverates_fx_map', array());
$key = get_woocommerce_currency() . strtoupper($currency);
return isset($map[$key]) ? $map[$key] : $rate;
}, 10, 2);A webhook variant uses the same function. Register a REST route that checks a shared secret, then call liverates_refresh_woo_rates() when the provider POSTs a threshold move (for example a 30-pip swing on EUR/USD). Keep the hourly cron as a backstop so a missed webhook cannot freeze rates overnight. Point the job at the pairs the checkout actually offers, not a vanity list of every ISO code.
Shopify multi-currency rates and app options
Shopify merchants have three realistic paths. Native Shopify Markets plus Shopify Payments is the least engineering: rates and conversion fees are Shopify's. That path fits a simple catalog when the conversion fee is accepted as a cost of doing business. It does not let the merchant choose the feed or the refresh cadence.
Existing currency apps geo-detect and convert from a built-in source. The important questions are whether that source is daily and whether the app accepts a custom endpoint. If it does, an hourly pull from a live FX API is enough. If it does not, displayed prices will drift from both the market and the gateway.
A custom or private app is the route when the store must own the number. A worker — Shopify Flow on a schedule for eligible plans, or an external cron — fetches rates and updates Market price lists or metafields through the Admin API. Theme code or a Shopify Function reads those metafields for display. Checkout still settles through Shopify Payments or the chosen gateway; an app cannot overwrite the processor FX rate. The job is to keep displayed prices and invoice-facing amounts honest, then reconcile display versus payout in finance, the same as on WooCommerce.
FAQ
Does WooCommerce include a built-in currency converter API?
No. WooCommerce core is single-currency. Conversion is provided by plugins that store manual rates or call an external FX API, and those plugins still need a scheduled job if rates should move more than once a day.
How often should Shopify multi-currency rates refresh?
Hourly is a sound default for catalogs that include volatile quote currencies. Native Shopify Markets refresh on Shopify's schedule and cannot be retuned; a custom app or a currency app with a custom feed is required to own cadence.
What is the difference between charge currency and settlement currency?
Charge currency is the amount and ISO code the customer authorizes, and it belongs on the invoice. Settlement currency is the amount deposited by the acquirer after its own FX. The two rates can differ, and that difference is margin or a dispute, not a rounding error.
Can the ECB daily rate be used at checkout?
It can, and many plugins default to it, but it is a once-per-business-day euro reference. It lags intra-day moves on volatile pairs and is a better input for accounting than for a live cart.
Will an hourly WooCommerce refresh change past invoices?
Not if the order stores the rate, pair, and timestamp at checkout. The cron should update only the live option used for new carts; historical orders must keep the frozen conversion.
Stores that need a dedicated feed for a WooCommerce currency converter or a Shopify multi-currency catalog can compare access and pair coverage on the Live-Rates plans page.
Real-time forex rates for your app
Live bid/ask for the pairs you need, updated every second, with a simple JSON & XML API. Try it free for 7 days.