Skip to main content
Thirdwatchthirdwatch
Companies & leads

Reconcile Cross-Border Payments With FX Rate Data

Pull dated exchange rates with named providers to reconcile what cross-border payments should have converted at versus what actually settled.

Editorial illustration for companies & leads
Sep 21, 2026 · 3 min read · 563 words
View the Apify scraper →

TL;DR — The Currency Rates Scraper gives you a dated, provider-named market rate for any day. Join it to payment settlements and the spread between expected and actual conversion becomes a measurable line item instead of a shrug.

Why cross-border reconciliation needs a benchmark rate

Every international payment has two rates: the one you assumed and the one that settled. The difference is spread plus timing, and it adds up — BIS counts $7.5 trillion in daily FX volume, and none of it moves at the rate your invoice guessed.

Without an independent benchmark, "the bank gave us a bad rate" is a feeling. With one — dated, provider-named — it is a number you can trend, dispute, or budget for.

How does this compare to the alternatives?

Trust the statement Paid market-data feed Thirdwatch actor
Cost Free but blind Expensive Pay per result
Reliability No benchmark Excellent Named free sources
Setup time None Weeks Minutes
Maintenance None Vendor-managed Scheduled pulls

A paid feed is overkill for reconciliation — you need a daily fix, not tick data. The actor gives exactly that, with provenance.

How to reconcile payments in 4 steps

How do I pull rates for a settlement period?

One range pull covers the whole month:

import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("thirdwatch/currency-rates-scraper").call(
    run_input={
        "baseCurrencies": ["USD"],
        "targetCurrencies": ["EUR", "GBP", "INR"],
        "startDate": "2026-08-01",
        "endDate": "2026-08-31",
        "maxResults": 250,
    }
)
rates = list(client.dataset(run["defaultDatasetId"]).iterate_items())

How do I join payments to the right rate?

Match each settlement to the rate on its date:

import pandas as pd

rates_df = pd.DataFrame(rates)
rates_df["date"] = pd.to_datetime(rates_df["date"]).dt.date
payments = pd.read_csv("settlements.csv")  # id, pair, date, expected, settled
merged = payments.merge(
    rates_df[["pair", "date", "rate", "provider_name"]],
    on=["pair", "date"], how="left")

Missing joins mark days where the provider had no fix — forward-fill or flag them deliberately.

How do I compute the variance?

merged["benchmark_amount"] = merged["amount"] * merged["rate"]
merged["variance_pct"] = (merged["settled"] / merged["benchmark_amount"] - 1) * 100
print(merged.groupby("pair")["variance_pct"].mean())

A consistent 2-4% gap is your processor's real spread. A sudden spike on one date is worth a support ticket — and the provider-named rate is your evidence.

How do I make it repeatable?

Schedule the pull monthly, append rates to a JSONL store, and the reconciliation becomes a join you rerun, not a project:

import json
with open("fx_benchmark.jsonl", "a") as f:
    for r in rates:
        f.write(json.dumps(r) + "\n")

Sample output

{"base_currency": "USD", "quote_currency": "GBP", "pair": "USD/GBP",
 "date": "2026-08-14", "rate": 0.7861, "inverse_rate": 1.2721,
 "amount": 1, "converted_amount": 0.7861,
 "provider": "erapi", "provider_name": "ExchangeRate-API open access"}

date keys the join; provider_name is the citation when you dispute a settlement.

Common pitfalls

Settlement date and quote date differ — decide which one your policy benchmarks and be consistent. Weekend settlements map to the nearest published fix; document the convention. A benchmark shows market rate, not your processor's rate — the gap is the finding, not a bug. The actor names the provider per row so variance reports survive a source switch.

Related use cases

Frequently asked questions

What is FX reconciliation in one sentence?

Comparing the exchange rate a payment was expected to convert at — a dated, sourced market rate — against the rate that actually settled, and explaining the difference.

Why does the settled amount differ from the quoted amount?

Processors add spread and fees on top of mid-market rates, and settlement can happen days after the quote. A dated benchmark rate separates real cost from noise.

Can I pull the rate for a past settlement date?

Yes. Set the date field to the settlement or quote day and the actor returns that day's published fix with the provider named.

How do I batch a month of payments?

Use startDate/endDate to pull the whole period's daily rates once, then join payments to the rate for each settlement date locally.

Related