Sanity bounds
Reject rates that fall outside reasonable historical ranges for the pair. Catches feed errors, decimal-shift bugs, and accidental inverse rates before they hit your storefront or invoice.
Catch bad rates before they reach checkout. This guide covers the four validation layers that protect margin across 150+ currencies — sanity bounds, freshness checks, cross-source comparison, and anomaly detection.
Reject rates that fall outside reasonable historical ranges for the pair. Catches feed errors, decimal-shift bugs, and accidental inverse rates before they hit your storefront or invoice.
Validate the rate timestamp on every read. Stale rates from a cache miss or a paused upstream feed cause more disputes than wrong rates.
Compare your primary feed against an independent reference. Flag when the spread between sources exceeds your tolerance — typically 0.25% for majors, 1% for exotics.
Track rolling volatility per pair and alert on outlier ticks. Especially important during market open / close and around macro events.
A rate suddenly off by 10× or 0.1× — almost always a parsing or formatting bug, never a real market move.
Code expects USD/EUR but receives EUR/USD. Easy to spot with sanity bounds; expensive when it ships to checkout unchecked.
A cached rate older than your TTL still gets returned because invalidation failed. Always check `asOf` against current time, not just trust the cache.
Two reputable feeds disagree by more than spread tolerance. Either one source is degraded, or genuine market dislocation — your monitoring should distinguish.
Majors (USD/EUR, USD/JPY, GBP/USD) tolerate ~0.25% cross-source spread. Minors / exotics need wider bounds (0.75–2%) because feed coverage thins out. Codify these in your validation layer.
Storefront price calc, checkout rate lock, refund recalculation, ERP rate sync, month-end reporting — each is an independent boundary that should re-run validation, not assume the feed was clean.
Store the exact rate and `asOf` timestamp on the order itself. Refunds and disputes can be answered without rebuilding the trail from feed history.
A failed validation that only writes to a log gets ignored until the dispute lands in support. Route validation failures to the same channel as your payment failures.
A minimal validation wrapper around the convert endpoint. Fails fast on stale or out-of-band rates so callers can fall back to a cached value or surface a clear error to the user.
type RateBound = { min: number; max: number };
const bounds: Record<string, RateBound> = {
'USD/EUR': { min: 0.7, max: 1.3 },
'USD/JPY': { min: 80, max: 200 },
'USD/GBP': { min: 0.5, max: 1.0 },
};
const MAX_AGE_MS = 60_000;
const MAX_CROSS_SOURCE_SPREAD = 0.0025; // 0.25%
export function validateRate(
pair: string,
rate: number,
asOf: Date,
reference?: number,
): { ok: true } | { ok: false; reason: string } {
const bound = bounds[pair];
if (bound && (rate < bound.min || rate > bound.max)) {
return { ok: false, reason: 'rate outside sanity bounds' };
}
if (Date.now() - asOf.getTime() > MAX_AGE_MS) {
return { ok: false, reason: 'rate is stale' };
}
if (reference !== undefined) {
const spread = Math.abs(rate - reference) / reference;
if (spread > MAX_CROSS_SOURCE_SPREAD) {
return { ok: false, reason: 'cross-source spread exceeded' };
}
}
return { ok: true };
}Most teams build the convert call and stop. Validation, alerting, and locked-rate reconciliation are the difference between an FX bug surfacing as a refund-month-later mystery and one that pages an engineer in 60 seconds. Wire these in on day one — they are far cheaper to add now than to retrofit during an incident.
Use the API to fetch live rates, lock at checkout, and reconcile refunds with the same source of truth.
Instant setup • 99.9% uptime SLA • <50ms response time