Convert each transaction into a reporting currency using its currency code, amount and an explicit rate date. In PostHog, the argument order is convertCurrency(from_currency, to_currency, amount, date). Then choose the right denominator: total revenue, average order value and revenue per experiment participant answer different questions.
This guide includes a synthetic query executed in PostHog, a purchase-event expression to adapt to your schema, and a reconciliation example covering repeat orders, refunds and nonbuyers. The examples contain no customer records.
First define what the amount means
Keep the original amount and currency in your source event. Establish whether the amount is gross or net, includes tax or shipping, and represents a completed payment rather than an attempted checkout. Record a stable transaction identifier so an ingestion retry does not become a second sale. Use your event tracking plan to document those choices.
The conversion function does not know whether an amount is stored in major or minor units. Convert minor units according to the source currency and payment system; do not divide every currency by 100. Missing currency codes, invalid amounts and duplicate records belong in a reconciliation report, not silently in a revenue total.
Verify the function with a synthetic query
The following constant-only query ran successfully in PostHog on September 8, 2026. It reads no events. The dates are deliberately fixed so the example compares historical conversion, rather than changing its question every day.
SELECT
convertCurrency('USD', 'USD', toDecimal('100.00', 10),
assumeNotNull(toDate('2025-01-15'))) AS usd_identity,
convertCurrency('EUR', 'USD', toDecimal('100.00', 10),
assumeNotNull(toDate('2025-01-15'))) AS eur_january,
convertCurrency('EUR', 'USD', toDecimal('100.00', 10),
assumeNotNull(toDate('2025-06-16'))) AS eur_june,
convertCurrency('EUR', 'USD', toDecimal('-25.00', 10),
assumeNotNull(toDate('2025-01-15'))) AS refund_januaryOutput | Observed value in USD
usd_identity | 100.0000000000
eur_january | 102.9365746001
eur_june | 115.5382812987
refund_january | −25.7341436500These are observed outputs from the tested environment, not guaranteed settlement rates or a substitute for your accounting policy. A payment processor can use different rates, fees and settlement dates. Keep more precision while calculating and round the displayed report at the end.
There was a useful failure before this query passed: using toDate directly on a date string produced a nullable-date error in the exchange-rate dictionary lookup. assumeNotNull resolved that issue for these known-valid literal dates. It is not validation for arbitrary input: reject invalid or missing dates before applying that pattern to incoming data.
Adapt the expression to a real purchase event
PostHog’s Revenue Analytics troubleshooting guide documents this conversion order and a SQL expression for Trends. For an event whose amount is in properties.revenue and currency is in properties.currency, the expression is:
sum(convertCurrency(
upper(properties.currency),
'USD',
toDecimal(properties.revenue, 10),
toDate(timestamp)
))Select the actual completed-purchase event and a bounded reporting period in Trends before using this expression. This event-based expression follows the documented pattern; it has not been executed against your event schema here. The synthetic query above verifies the function separately from your capture and filtering setup.
For a nested event shape, replace the source currency and amount with the actual verified paths—for example, properties.totalPrice.currencyCode and properties.totalPrice.amount. Do not combine flat and nested paths opportunistically unless you have established how conflicting or missing values should be handled.
Check a small sample against the billing source first. Confirm event timestamps represent the intended transaction date and that the project’s date boundaries match your reporting policy. In a historical report, explicitly supplying the date prevents an omitted date from silently turning the question into conversion at the current rate. Upstream HogQL implementation supplies today when that argument is omitted; use an explicit date rather than relying on that default.
Reconcile a small ledger before trusting the total
The table below is an invented ledger for checking the method. Its EUR conversions use the successful query above. A refund reverses part of the original purchase at the original conversion date in this example; a settlement-date policy would be a different, explicitly documented calculation.
Record | Treatment | USD contribution
USD order: 100 | Include once | 100.0000000000
EUR order: 100, January date | Include once | 102.9365746001
Repeat EUR order: 100, June date | A separate genuine purchase | 115.5382812987
EUR refund: 25, original January rate | Subtract once | −25.7341436500
Retry of the January order | Same transaction; exclude duplicate | 0
Participant with no orders | Include in participant denominator | 0For this ledger, gross revenue is approximately $318.4749 and refund-adjusted revenue is approximately $292.7407. A positive-amount filter is appropriate only for the gross-purchase calculation; applying it to a signed purchase/refund ledger would discard the refund. Avoid counting both a net order amount and its separate refund event.
The duplicate rule needs a transaction identity and a policy for updates or partial captures. Deduplicating by amount, email or timestamp alone can discard legitimate repeat purchases. Unsupported currencies and absent historical rates also need explicit failure checks: never interpret a conversion result of zero as proof that the original sale had no value.
Separate order value from experiment revenue
Average order value divides gross revenue by completed orders. In the synthetic ledger, that is approximately $318.4749 / 3 = $106.1583 per order. It excludes people who never bought. A treatment could raise that average while reducing the number of buyers.
Revenue per experiment participant includes everyone assigned to the relevant arm, including nonbuyers. If the three orders above came from two buyers among four assigned participants, refund-adjusted revenue per participant would be approximately $292.7407 / 4 = $73.1852. This illustrative denominator is part of the invented example, not a client result.
For an experiment, preserve assignment, sum each participant’s eligible transactions over the same follow-up window, subtract refunds according to the agreed policy, and include zero-revenue participants. Check identity joins, exposure timing, repeat purchases and cohort maturity. A raw arm total or event-level mean does not establish this metric automatically. Use the A/B testing metrics guide to choose the business outcome, and verify the current PostHog metric configuration before launch.
A practical acceptance check
Before using the report for a decision, reconcile original currency totals to the payment source; verify a same-currency transaction and two historical dates; check a genuine repeat order, a duplicate and a partial refund; and confirm whether the denominator is orders or assigned participants. Record missing or rejected records separately. The PostHog audit checklist provides the wider capture and identity checks.
The result should explain both the money and the population behind it. Correct currency conversion is necessary, but it cannot repair missing purchases, inconsistent refund handling or the wrong experiment denominator.



