WooCommerce draait grofweg een derde van alle online winkels ter wereld, en zodra een van die winkels grensoverschrijdend wil verkopen, duikt dezelfde vraag op: hoe houd je prijzen, btw en uitbetalingen eerlijk in twaalf valuta zonder elke ochtend handmatig de wisselmarkt te checken? Het eerlijke antwoord is een WooCommerce multi-currency wisselkoers-API aansluiten op je winkel — maar dat goed doen is genuanceerder dan een plugin installeren en weglopen. Je moet nadenken over refresh-cadans, cache stampedes, payment-gatewaycompatibiliteit, afronding en wat er gebeurt als de upstream halverwege een checkout een 503 teruggeeft.
Deze gids loopt drie productiepatronen door die developers in 2026 daadwerkelijk gebruiken, met werkende PHP voor elk. We koppelen de Finexly-API op drie manieren aan WooCommerce: als custom feed voor een bestaande currency-switcher-plugin, als from-scratch filterlaag, en als Action Scheduler-job met fatsoenlijke locking. Aan het eind heb je een patroon dat past bij elke stack — kale WooCommerce, WooCommerce + switcher-plugin, of een volledig custom B2B-build.
Waarom een Realtime Wisselkoers-API Belangrijk Is voor WooCommerce
Out of the box zet WooCommerce één basisvaluta in woocommerce_currency en gaat ervan uit dat elke order, elke refund en elke payout in die valuta wordt afgewikkeld. Multi-currency-ondersteuning is een laag erbovenop: een switcher in de header, herberekende cart-totalen en (soms) gelokaliseerde betaalverwerking. Die laag heeft ergens wisselkoersen vandaan nodig, en "ergens" is meestal een van twee dingen — een handmatig ingevoerde koers die niemand meer bijwerkt, of een gratis providerfeed die om de 12-24 uur tikt.
Beide zijn gevaarlijk in het FX-landschap van 2026. Paren als USD/JPY bewogen op 6 mei alleen al 0,95% na de interventie van Tokio, en emerging-market-valuta zwaaien geregeld meer dan 1% intraday. Een winkel die een 24 uur oude koers laat zien, verkoopt afhankelijk van de wind met 1-2% korting of opslag — en dat is voor de spread die het gateway erbij zet bij settlement. Een realtime WooCommerce multi-currency wisselkoers-API met een fatsoenlijke cachelaag houdt je getoonde prijzen altijd binnen fracties van een procent van mid-market.
De andere reden waarom dit telt: payment-gatewaycompatibiliteit. Stripe, PayPal en de meeste grote gateways hebben hun eigen lijst met ondersteunde settlement-valuta. Als je winkel prijzen in 15 valuta toont maar Stripe alleen settlet in 6, moet je weten welke conversie waar gebeurt en tegen welke koers — en die berekening is veel makkelijker als jouw laag de bron van waarheid voor FX is, en niet een black-box-gateway.
De Drie Integratiepatronen
Er zijn drie plekken waar je een currency-API in een WooCommerce-winkel kunt prikken, elk met zijn eigen afwegingen:
- Aanhaken op een bestaande switcher-plugin — snelste route, werkt met CURCY, FOX, Aelia, VillaTheme en de meeste populaire plugins. Code is minimaal maar je zit vast aan het update-ritme van de plugin en zijn storefront-UI.
- Custom filterlaag op WooCommerce-core-hooks — volledige controle over welke prijzen worden geconverteerd (regulier, sale, verzending, btw), maar je schrijft meer code en de UI is van jou.
- Hybride: plugin doet UI, jouw code doet koersen — best of both worlds voor winkels die al op een switcher draaien. De plugin rendert de switcher; jouw geplande job duwt koersen in de opslag van de plugin.
We bouwen alle drie.
Patroon 1: Custom Koersfeed voor een Bestaande Switcher-Plugin
De meeste populaire WooCommerce-currency-plugins exposen een filter of action-hook voor custom koersbronnen. VillaTheme's CURCY gebruikt wmc_get_currency_exchange_rates. FOX gebruikt woocs_currencies_array. Aelia gebruikt wc_aelia_cs_exchange_rates_request_args. Het patroon is overal hetzelfde — je vangt de koersen die de plugin anders bij zijn ingebouwde providers zou ophalen, en vervangt ze door die van jou.
Hier is een compleet voorbeeld voor CURCY (zelfde vorm werkt voor FOX met een andere filternaam). Plak het in een kleine mu-plugin of in de functions.php van je theme:
<?php
/**
* Plugin Name: Finexly Rates for CURCY
* Description: Pulls real-time WooCommerce multi-currency exchange rates from Finexly.
*/
add_filter( 'wmc_get_currency_exchange_rates', 'finexly_curcy_rates', 10, 1 );
function finexly_curcy_rates( $rates ) {
$cached = get_transient( 'finexly_curcy_rates' );
if ( false !== $cached ) {
return wp_parse_args( $cached, $rates );
}
$base = get_woocommerce_currency(); // e.g. "USD"
$symbols = array_keys( $rates ); // e.g. ["EUR","GBP","JPY",...]
$api_key = defined( 'FINEXLY_API_KEY' ) ? FINEXLY_API_KEY : '';
if ( empty( $api_key ) || empty( $symbols ) ) {
return $rates; // graceful fall-through to plugin defaults
}
$url = add_query_arg( array(
'apikey' => $api_key,
'base' => $base,
'currencies' => implode( ',', $symbols ),
), 'https://api.finexly.com/v1/latest' );
$response = wp_remote_get( $url, array( 'timeout' => 8 ) );
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
return $rates;
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( empty( $body['rates'] ) ) {
return $rates;
}
// CURCY expects an associative array of CODE => rate (relative to base).
$fresh = array();
foreach ( $body['rates'] as $code => $rate ) {
$fresh[ $code ] = (float) $rate;
}
set_transient( 'finexly_curcy_rates', $fresh, HOUR_IN_SECONDS );
return wp_parse_args( $fresh, $rates );
}Een paar details die ertoe doen. Ten eerste heeft de wp_remote_get-call een timeout van 8 seconden — lang genoeg om een trage respons op te vangen, kort genoeg dat PHP-FPM niet omvalt als de upstream haperend is. Ten tweede geven we bij welke fout dan ook (netwerkfout, niet-200, ontbrekende payload) de default-$rates van de plugin terug in plaats van een lege array. Een gedegradeerde checkout is beter dan een kapotte. Ten derde caches de transient geslaagde responses een uur, wat voor retail-e-commerce ruim vers genoeg is en je comfortabel onder elke API-quota houdt.
Definieer je key in wp-config.php:
define( 'FINEXLY_API_KEY', 'your_api_key_here' );Dat is het. CURCY's switcher rendert in de header, klanten kiezen een valuta, en prijzen worden herberekend met verse koersen uit jouw API-call in plaats van de default 12-uurs Yahoo Finance-feed van CURCY.
Patroon 2: From-Scratch Multi-Currency met Core WooCommerce-Filters
Wil je geen plugin in de loop — of bouw je een custom B2B-shop waar de switcher onderdeel is van een account-instelling in plaats van een dropdown in de header — dan kun je de hele multi-currency-laag bouwen met twee core-filters: woocommerce_currency en raw_woocommerce_price.
Met woocommerce_currency wissel je per request de actieve valuta. Met raw_woocommerce_price transformeer je elke prijs die WooCommerce laat zien. Combineer dat met een sessievariabele en je hebt in zo'n 60 regels een werkende currency-switcher:
<?php
class Finexly_WC_Currency {
const SESSION_KEY = 'finexly_active_currency';
const TRANSIENT = 'finexly_rates';
public function __construct() {
add_action( 'init', array( $this, 'maybe_switch_currency' ) );
add_filter( 'woocommerce_currency', array( $this, 'active_currency' ) );
add_filter( 'raw_woocommerce_price', array( $this, 'convert_price' ) );
add_filter( 'woocommerce_package_rates', array( $this, 'convert_shipping' ), 10, 2 );
}
public function maybe_switch_currency() {
if ( isset( $_GET['set_currency'] ) ) {
$code = strtoupper( sanitize_text_field( $_GET['set_currency'] ) );
if ( array_key_exists( $code, $this->rates() ) ) {
WC()->session->set( self::SESSION_KEY, $code );
}
}
}
public function active_currency( $default ) {
$code = WC()->session ? WC()->session->get( self::SESSION_KEY ) : null;
return $code ?: $default;
}
public function convert_price( $price ) {
$code = $this->active_currency( get_option( 'woocommerce_currency' ) );
$rates = $this->rates();
if ( ! isset( $rates[ $code ] ) ) {
return $price;
}
return (float) $price * (float) $rates[ $code ];
}
public function convert_shipping( $rates, $package ) {
$code = $this->active_currency( get_option( 'woocommerce_currency' ) );
$fx = $this->rates();
if ( ! isset( $fx[ $code ] ) ) {
return $rates;
}
foreach ( $rates as $rate ) {
$rate->cost = (float) $rate->cost * (float) $fx[ $code ];
foreach ( $rate->taxes as &$tax ) {
$tax = (float) $tax * (float) $fx[ $code ];
}
}
return $rates;
}
private function rates() {
$cached = get_transient( self::TRANSIENT );
return is_array( $cached ) ? $cached : array();
}
}
new Finexly_WC_Currency();De transient wordt gevuld door een aparte geplande job (volgende sectie), dus deze klasse doet nooit een netwerk-call binnen een request — elke pageload is één enkele get_transient-lookup, in omgevingen met object cache ~0,1 ms. Dat is het verschil tussen 8 ms aan TTFB toevoegen op elke uncached pagina en 800 ms één keer per uur aan één cron-worker.
Twee dingen om voor productie toe te voegen: precisie en afronding. WooCommerce slaat prijzen op als decimal; een decimal(13,4)-waarde vermenigvuldigen met een koers 1.10851234 en weer opslaan als decimal(13,4) laat stilletjes precisie wegvallen. Voor laaggeprijsde items (sub-$1-SKU's, fooienpotjes, micro-donaties) wil je intern minimaal 8 decimalen precisie, en pas op de displaylaag afronden naar de natuurlijke minimumeenheid van de valuta. WooCommerce's wc_price()-helper handelt de display-afronding voor je af; wat jij moet doen is de onderliggende floats breed genoeg houden.
Patroon 3: Action-Scheduler-Refreshjob met Stampede-Bescherming
De twee patronen hierboven lezen uit een transient. Iets moet in die transient schrijven. De juiste tool in WooCommerce is Action Scheduler — de jobrunner die met WooCommerce wordt meegeleverd — omdat hij betrouwbaar is, automatisch retried en concurrency beter aankan dan wp_schedule_event.
<?php
/**
* Plugin Name: Finexly Rates Refresh
* Description: Refreshes WooCommerce multi-currency exchange rates from Finexly hourly.
*/
add_action( 'init', function () {
if ( false === as_next_scheduled_action( 'finexly_refresh_rates' ) ) {
as_schedule_recurring_action(
time() + 60,
HOUR_IN_SECONDS,
'finexly_refresh_rates',
array(),
'finexly'
);
}
} );
add_action( 'finexly_refresh_rates', 'finexly_do_refresh' );
function finexly_do_refresh() {
// Stampede lock: only one worker at a time.
$lock_key = 'finexly_refresh_lock';
if ( get_transient( $lock_key ) ) {
return;
}
set_transient( $lock_key, 1, 30 );
try {
$base = get_option( 'woocommerce_currency', 'USD' );
$supported = apply_filters( 'finexly_currencies', array(
'USD','EUR','GBP','JPY','CHF','CAD','AUD','CNY','INR','BRL','MXN','SEK','NOK','SGD','HKD','TRY','ZAR','PLN'
) );
$url = add_query_arg( array(
'apikey' => FINEXLY_API_KEY,
'base' => $base,
'currencies' => implode( ',', $supported ),
), 'https://api.finexly.com/v1/latest' );
$response = wp_remote_get( $url, array( 'timeout' => 10 ) );
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
// Don't blow away existing rates on a transient upstream failure.
error_log( 'Finexly refresh failed: ' . wp_remote_retrieve_response_code( $response ) );
return;
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( empty( $body['rates'] ) ) {
return;
}
$rates = array_map( 'floatval', $body['rates'] );
$rates[ $base ] = 1.0;
set_transient( 'finexly_rates', $rates, 3 * HOUR_IN_SECONDS );
// Persist to options as a durable fallback. Object cache flushes
// would otherwise wipe transients and force a cold checkout.
update_option( 'finexly_rates_persistent', array(
'fetched_at' => time(),
'base' => $base,
'rates' => $rates,
), false );
} finally {
delete_transient( $lock_key );
}
}Drie patronen die het waard zijn om te benoemen. De stampede-lock voorkomt dat twee parallelle workers de upstream rammeien wanneer Action Scheduler laat afgaat en twee jobs achter elkaar in de queue zet. De transient-TTL ×3 versus refresh-interval van een uur is een bewuste buffer — als één refresh faalt, overleeft de gecachte waarde tot de volgende poging. De persistente fallback in options is de derde laag: als de object cache om welke reden dan ook wordt geleegd, heeft de winkel nog steeds een bruikbare ratenset van maximaal 24 uur oud, en kan je rates()-methode daar op terugvallen voordat hij leeg teruggeeft.
Wil je dieper de cache-architectuur in, dan zijn de patronen uit onze gids over caching en error handling voor currency-API's één-op-één toepasbaar op WooCommerce.
Je Multi-Currency-Integratie Testen
WooCommerce-shops gaan in productie op drie voorspelbare manieren stuk, en alle drie zijn testbaar. Hier is een minimale PHPUnit-suite met WP_Mock en Mockery:
<?php
class Finexly_WC_Currency_Test extends WP_Mock\Tools\TestCase {
public function test_convert_price_with_known_rate() {
WP_Mock::userFunction( 'get_transient' )
->with( 'finexly_rates' )
->andReturn( array( 'EUR' => 0.92, 'GBP' => 0.79 ) );
WP_Mock::userFunction( 'get_option' )
->with( 'woocommerce_currency' )
->andReturn( 'USD' );
$svc = new Finexly_WC_Currency();
$svc->set_active_currency( 'EUR' );
$this->assertEqualsWithDelta( 92.0, $svc->convert_price( 100.0 ), 0.0001 );
}
public function test_returns_original_price_when_rate_missing() {
WP_Mock::userFunction( 'get_transient' )
->andReturn( array() );
$svc = new Finexly_WC_Currency();
$svc->set_active_currency( 'XYZ' ); // unsupported code
$this->assertEquals( 100.0, $svc->convert_price( 100.0 ) );
}
public function test_refresh_handles_upstream_500() {
WP_Mock::userFunction( 'wp_remote_get' )
->andReturn( array( 'response' => array( 'code' => 500 ) ) );
WP_Mock::userFunction( 'set_transient' )->never();
WP_Mock::userFunction( 'update_option' )->never();
finexly_do_refresh();
// Test passes if no fatal error and we don't overwrite cached rates.
}
}De derde test is degene die de meeste teams overslaan. Bijna elke WooCommerce-currency-bug die ik in productie heb gezien reduceert tot "de upstream gaf een fout terug en we cachten het lege antwoord." Vaststellen dat je geen rotzooi schrijft bij een fout is waardevoller dan vaststellen dat je het juiste schrijft bij succes.
Veelvoorkomende Valkuilen en Hoe Ze te Vermijden
Gateway-valuta-ondersteuning. Dat je shop prijzen in TRY toont, betekent niet dat Stripe in TRY settlet. Voordat je een valuta in je switcher exposet, check WC()->payment_gateways() en filter op de doorsnede van valuta die je actieve gateways ondersteunen. De vergelijking tussen Stripe FX Quotes API en een dedicated currency-API gaat in detail in op de afwegingen.
Refunds en orderaanpassingen. Een refund in EUR drie weken na een USD-base-order gebruikt niet de koers van vandaag — die gebruikt de koers die de gateway op het moment van de refund pakt. Sla de koers op het moment van de order op in order-meta (update_post_meta( $order_id, '_finexly_rate', $rate )) zodat reporting kan reconcilien. Dat is ook handig voor de accounting-software-integratielaag.
Afronding. Rond af bij display, niet bij opslag. 9.99 * 0.92 = 9.1908 afronden naar 9,19 en terugconverteren naar USD geeft 9,989..., niet 9,99. Brede interne precisie plus afronding op het moment van weergave is het enige gezonde patroon.
ISO-4217-validiteit. Vertrouw geen user-input. Valideer alle valutacodes tegen de ISO-4217-lijst voordat je ze in de sessie schrijft.
API Kiezen: Gratis vs. Betaald
WooCommerce's ingebouwde providers (Open Exchange Rates free tier, het deprecated CurrencyLayer enz.) refreshen om de 12-24 uur en zitten op kleine quota's. Voor een kleine shop met één product en tien orders per maand prima; voor alles met traffic wil je een dedicated provider met minstens uurlijkse refresh en een quota die je niet dwingt op je calls te beknibbelen. Onze gratis vs. betaald currency-API-vergelijking voor 2026 geeft de echte cijfers.
De Finexly-API mikt precies op dit gebruik — 170+ valuta, 60-seconden refresh op majors, uurlijks op long-tail, en een free tier van 1.000 requests/maand die ruim genoeg is voor één Action Scheduler-job per uur met retry-marge. Je kunt currency-API's vergelijken of je gratis aanmelden om het in een dev-shop te testen.
Veelgestelde Vragen
Ondersteunt WooCommerce multi-currency native?
Gedeeltelijk. WooCommerce ondersteunt één basisvaluta voor orders en payouts. Multi-currency-weergave en checkout vragen om óf een switcher-plugin (CURCY, FOX, Aelia enz.) óf custom code met de filters woocommerce_currency en raw_woocommerce_price, en beide aanpakken hebben een door jou geleverde koersbron nodig.
Hoe vaak moet ik de koersen in WooCommerce verversen? Voor retail e-commerce is uurlijks ruim voldoende. Voor B2B-shops met hoge factuurbedragen is 15-30 minuten veiliger. Agressiever loont zelden — payment-gateways re-quoten bij settlement toch, dus het verschil tussen een 5-minuten-koers en een 30-minuten-koers verzuipt in de gateway-spread.
Kan ik een custom currency-API integreren met de switcher-plugin die ik al gebruik?
Ja. CURCY exposed wmc_get_currency_exchange_rates, FOX exposed woocs_currencies_array, Aelia heeft wc_aelia_cs_exchange_rates_request_args. Hook in op het filter, geef je koersen terug en de UI van de plugin blijft werken zoals ie was.
Wat gebeurt er als de currency-API down is tijdens een checkout? Met de architectuur hierboven: niets zichtbaars voor de klant. De pricing-laag leest een transient die door een background-job wordt gevuld; als de laatste refresh faalde, staat de vorige koers nog in cache en houdt de persistente option een 24-uurs fallback bij. De checkout gaat door op de laatst bekende goede koers.
Hoe ga ik om met refunds tussen valuta? Sla de koers op het moment van de order op in order-meta. Bereken bij een refund het bedrag met die koers, niet die van vandaag, zodat de klant heel wordt gemaakt in de valuta waarin hij betaalde. De FX-P&L kan je accountingsysteem apart reconcilien.
Tot Slot
Een solide WooCommerce multi-currency wisselkoers-API-integratie draait niet echt om de API-call — het draait om de vier lagen eromheen: caching, scheduling, fallbacks en tests. Die op orde en je shop houdt eerlijke prijzen in 50+ valuta zonder ook maar één pager-alert in de nacht.
Klaar om realtime wisselkoersen in je WooCommerce-winkel te integreren? Vraag je gratis Finexly-API-key aan — geen creditcard nodig. Begin met 1.000 gratis requests per maand, plak de snippets hierboven in een mu-plugin en je hebt multi-currency-pricing live voor de lunch. Groei je voorbij het free tier, dan schalen de pricing-plannen lineair met je shop, niet exponentieel.
Explore More
Vlado Grigirov
Senior Currency Markets Analyst & Financial Strategist
Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...
View full profile →