Tilbage til blog

How to Handle Currency Conversion for International Contractor Payments

V
Vlado Grigirov
August 13, 2026
Currency API Exchange Rates Contractor Payments Multi-Currency Payouts Developer Guide Finexly

Paying a developer in Lagos, a designer in Buenos Aires, and a copywriter in Manila from a single USD balance sounds like a payments problem. It isn't — not really. The payment rail is a solved commodity. The part that quietly leaks money and generates support tickets is currency conversion for international contractor payments: deciding which currency to pay in, which exchange rate to apply, when to lock that rate, and how to store it so your books reconcile months later. This guide is for the backend engineer who owns the payouts code and just got handed a ticket like "let contractors get paid in their local currency."

The stakes are real and growing. By 2027, an estimated 86.5 million people in the U.S. will be freelancing, and the global independent workforce is projected to reach 1.57 billion. At the same time, hidden fees on cross-border payments can inflate costs by 20–40%: SWIFT wires alone add $15–45 per payment plus a 2–4% FX markup, and some freelance platforms stack fees up to 10%. Most of that margin hides in the exchange rate. If you control the conversion layer yourself with a clean currency API, you control the number that matters most to your contractors — and to your finance team.

Why contractor payouts are really a currency-data problem

When you send $1,000 to a contractor who invoices in Philippine pesos, three separate things happen: your platform decides how many pesos $1,000 represents, a payment provider moves the money, and the contractor's bank credits their account. Only the middle step is "payments." The first step — the conversion — is a data problem, and it's the one your application is responsible for.

Get it wrong and the failure modes are specific. Show a contractor a payout estimate of ₱58,000 in your dashboard, then settle ₱56,200 two days later because the rate moved, and you've created a trust problem. Apply an opaque, padded rate and your contractors will eventually compare it to the mid-market rate and feel nickel-and-dimed. Fail to store the exact rate you used and your finance team can't reconcile the payout run against your ledger at month-end. Every one of these is an exchange-rate decision your code makes, deliberately or by accident.

The three FX decisions every payout system must make

Before writing any code, get explicit about three decisions. Most buggy payout systems are buggy because one of these was made implicitly.

  1. Which currency do you pay in? The contractor's local currency (best experience, you carry the FX), a hard currency like USD or EUR (you offload FX to their bank, usually at a worse rate for them), or a stablecoin. Store a payout_currency per contractor rather than assuming.
  2. Which rate do you apply? The mid-market rate is the honest reference point. On top of it you may add a transparent margin to cover provider spread. What you must never do is apply a padded rate and call it "the exchange rate."
  3. When do you lock the rate? At invoice approval, at batch creation, or at execution. The gap between these moments is where volatility bites. Whichever you choose, the locked rate must be the one you display, the one you settle, and the one you store.

Building the conversion layer, step by step

Let's build the core of a payout conversion service. The pattern is the same whether you're paying one contractor or ten thousand: fetch a trustworthy rate, apply a transparent margin, compute the amount, and persist the rate you used.

Step 1: Fetch a reliable mid-market rate

Start with the raw rate. Here's a direct call to the Finexly API with cURL:

curl "https://api.finexly.com/v1/latest?base=USD&symbols=PHP,ARS,NGN&apikey=YOUR_API_KEY"

A typical response:

{
  "base": "USD",
  "timestamp": 1755072000,
  "rates": {
    "PHP": 58.12,
    "ARS": 1287.40,
    "NGN": 1531.75
  }
}

In Python, wrap that in a small function that returns a decimal you can do money math with:

import requests
from decimal import Decimal

API_KEY = "YOUR_API_KEY"

def get_rate(base: str, quote: str) -> Decimal:
    resp = requests.get(
        "https://api.finexly.com/v1/latest",
        params={"base": base, "symbols": quote, "apikey": API_KEY},
        timeout=10,
    )
    resp.raise_for_status()
    return Decimal(str(resp.json()["rates"][quote]))

Always use Decimal, never float, for currency math. Floating-point rounding errors are invisible on one payout and very visible across a run of 5,000.

Step 2: Apply a transparent margin

If you need to cover a provider's spread, add it as an explicit, auditable markup rather than hiding it inside the rate:

def payout_amount(usd_amount: Decimal, base: str, quote: str,
                  margin_pct: Decimal = Decimal("0.5")) -> dict:
    mid = get_rate(base, quote)
    applied = mid * (1 - margin_pct / 100)     # margin works against the payee
    gross = (usd_amount * applied).quantize(Decimal("0.01"))
    return {
        "mid_market_rate": mid,
        "margin_pct": margin_pct,
        "applied_rate": applied.quantize(Decimal("0.000001")),
        "payout_local": gross,
    }

Returning the mid-market rate, the margin, and the applied rate separately means a contractor (or an auditor) can always see exactly how the number was built. Transparency here is a competitive advantage: it's the opposite of the "up to 10% in total fees" that drives contractors away from opaque platforms.

Step 3: Lock and store the rate

The rate you show at approval must equal the rate you settle. Persist it the moment you lock it:

quote = payout_amount(Decimal("1000.00"), "USD", "PHP")
# store alongside the payout record
save_payout(
    contractor_id=4471,
    usd_amount=Decimal("1000.00"),
    payout_currency="PHP",
    applied_rate=quote["applied_rate"],
    mid_market_rate=quote["mid_market_rate"],
    locked_at=datetime.utcnow(),
)

That stored applied_rate is the single most important field in your payouts table. It's what makes the payout auditable, what you reconcile against, and what you show the contractor if they ask why they received exactly that amount.

Converting a whole payment run in one batch

Paying contractors one at a time hammers the API and invites inconsistency — two contractors in the same run getting different USD/EUR rates because their requests fired a minute apart. Instead, pull all the rates you need in a single call, then apply them across the run so every payout in a batch uses the same rate snapshot:

from decimal import Decimal
import requests

def batch_convert(payouts: list[dict], base: str = "USD") -> list[dict]:
    symbols = ",".join(sorted({p["currency"] for p in payouts}))
    rates = requests.get(
        "https://api.finexly.com/v1/latest",
        params={"base": base, "symbols": symbols, "apikey": API_KEY},
        timeout=10,
    ).json()["rates"]

    out = []
    for p in payouts:
        rate = Decimal(str(rates[p["currency"]]))
        local = (Decimal(str(p["usd"])) * rate).quantize(Decimal("0.01"))
        out.append({**p, "rate": rate, "local_amount": local})
    return out

run = batch_convert([
    {"contractor_id": 4471, "usd": "1000.00", "currency": "PHP"},
    {"contractor_id": 5522, "usd": "750.00",  "currency": "ARS"},
    {"contractor_id": 6033, "usd": "1200.00", "currency": "NGN"},
])

One rate snapshot per run gives you a clean, defensible reconciliation story: every payout in batch #8821 used the rates captured at one timestamp. When you scale into thousands of payouts per cycle, this pattern also keeps you well inside sane rate limits — check the pricing plans for the request volumes each tier supports.

Handling volatility between approval and execution

The dangerous gap is the time between when you promise an amount and when the money actually moves. In fast-moving currencies that window can shift the payout by a percentage point or more. Three defensible strategies:

  • Lock at approval. Capture the rate when the payout is approved and honor it at execution, absorbing small moves yourself. Best contractor experience; you carry the FX risk.
  • Lock at execution. Compute the amount at the moment of disbursement. You carry no risk, but the contractor's final amount may differ from the estimate they saw.
  • Lock with a tolerance band. Lock at approval but re-check at execution; if the rate has moved beyond, say, 1.5%, flag the payout for review instead of silently settling a different amount.

A quick tolerance check in JavaScript:

async function withinTolerance(currency, lockedRate, tolerancePct = 1.5) {
  const res = await fetch(
    `https://api.finexly.com/v1/latest?base=USD&symbols=${currency}&apikey=YOUR_API_KEY`
  );
  const { rates } = await res.json();
  const drift = Math.abs((rates[currency] - lockedRate) / lockedRate) * 100;
  return { ok: drift <= tolerancePct, drift: drift.toFixed(2) };
}

Whichever model you pick, document it in your contractor agreement so expectations are set before anyone disputes a number.

Store the rate you used: reconciliation and compliance

Weeks after a payout, someone in finance will need to answer "what rate did we pay contractor 4471 on August 6?" or a contractor will query their amount. If you only stored the local amount, you can't reconstruct the answer. If you stored the rate, you can — and you can verify it against an independent source using the historical endpoint:

curl "https://api.finexly.com/v1/historical?date=2026-08-06&base=USD&symbols=PHP&apikey=YOUR_API_KEY"

This is the same discipline that governs cross-border payroll and marketplace payouts: the rate is a first-class piece of financial data, not a throwaway intermediate value. Store, for every payout, the base currency, payout currency, mid-market rate, margin, applied rate, and lock timestamp. Full API details are in the Finexly API documentation.

Common pitfalls to avoid

  • Using float for money. Rounding drift compounds across a batch. Use fixed-point decimals everywhere.
  • Fetching rates per payout in a loop. Inconsistent rates within one run and needless API load. Pull one snapshot per batch.
  • Hiding your margin inside the rate. Contractors will discover it. Show the mid-market rate and your margin separately.
  • Not storing the applied rate. You lose the ability to reconcile or explain a payout after the fact.
  • Ignoring the approval-to-execution gap. In volatile currencies this silently changes what contractors receive. Lock deliberately.
  • Assuming every contractor wants local currency. Some prefer USD or a stablecoin. Store a per-contractor preference.

Frequently asked questions

What exchange rate should I use to pay international contractors? Start from the mid-market rate — the real midpoint between buy and sell prices — as your honest reference. If you need to cover provider costs, add a small, explicitly disclosed margin on top rather than padding the rate itself. Contractors trust transparent math far more than a single opaque number.

Should I pay contractors in their local currency or in USD? Paying in local currency gives contractors the best experience because they know exactly what lands in their account, but it means your platform carries the FX conversion. Paying in USD offloads conversion to their bank, which usually gives them a worse rate. The best systems store a per-contractor currency preference and support both.

How do I keep the payout amount consistent across a large batch? Fetch all the currency rates you need in a single API call at the start of the run, then apply that one snapshot to every payout in the batch. This guarantees two contractors in the same run get the same USD-to-EUR rate and gives you one timestamp to reconcile against.

How do I avoid the hidden fees that inflate contractor payments by 20–40%? Most of that inflation lives in FX markup and per-transfer charges. Owning the conversion layer with a transparent rate source lets you show contractors the mid-market rate and exactly what margin, if any, you apply — instead of the 2–4% wire markups and up-to-10% platform fees baked into many off-the-shelf tools.

What data should I store for each contractor payout? At minimum: the base currency, the payout currency, the mid-market rate, any margin applied, the final applied rate, the local amount, and the timestamp you locked the rate. That record is what makes the payout auditable and reconcilable months later.

Ready to build a payout conversion layer your contractors and your auditors both trust? Get your free Finexly API key — no credit card required. Start with 1,000 free requests per month, pull real-time and historical rates for 170+ currencies, and upgrade as your payout volume grows. You can also try a quick conversion in our currency converter to see the data first-hand.

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 →