River
Y CombinatorBacked by Y Combinator

Business & Revenue OpsFree

Reconcile Two Reports With Different Numbers

Both exports in, every difference out classified and signed, and the classifications have to sum to the gap before the meeting.

Start here

River's reconciliation reads both exports rather than asking which one you trust. Two files go in at whatever grain they came out. What comes back is a sheet with one row per difference. Each is classified as a filter, a date range, a definition, a duplicate-handling rule or a genuinely missing record, and signed so its direction is visible. The classified differences are then summed against the headline gap, and anything left over is reported as unexplained rather than quietly absorbed.

The published answers to this question are good, and they all stop in the same place. The best of them reaches the right diagnosis, that the numbers are usually both correct within their own context, and then prescribes an architecture and governance project. That is true, and it is the correct long-term fix. It is not an answer for a meeting in ninety minutes, and it does not tell you which of the two numbers to say out loud once you are in the room.

Built for the analytics or operations person who owns the report and gets the message an hour before the meeting, and for the finance or revenue lead who has to defend the number afterwards. Reach for it when two systems disagree and the disagreement has become someone else's problem. Where the two sides are a statement and a ledger rather than two dashboards, the bank statement reconciliation fits better, and a report that is short because its export truncated needs the full Search Console export instead.

An explanation has to add up to the gap

Here is the shape of it. Google Ads reports 14,206 clicks on a campaign for August; GA4 reports 11,483 sessions from the same campaign over the same month. The gap is 2,723, or 19 percent of the larger number. The first cause anyone finds is repeat clicks inside one session, and it is real: 1,914 of them. That is 70 percent of the gap and it sounds like the answer. It leaves 809 unaccounted for, and two of the remaining causes push the other way.

The rest is smaller and more interesting. There were 1,043 clicks whose gclid never produced a session at all, 186 extra sessions from people returning later by bookmark, and 83 sessions a stale report filter dropped. A date boundary moved another 131, because the Ads account clock runs seven hours behind the property's. Signed and summed, the five come to 2,723 exactly. Four of them are published behavior: Google documents that Ads measures clicks while Analytics measures sessions and lists why they diverge.

Some of a gap is permanent, which changes what a reconciliation is for. GA4 does not agree with itself: standard reports estimate distinct counts with HyperLogLog++, BigQuery computes them precisely, and Google's own example shows 1,463 sessions in reports against 1,501 in BigQuery for one week. So the useful output is three numbers rather than one: the part explained, the part expected by design, and the residual. Only the third is a problem.

How it works

  1. Hand over both exports

    Two files at whatever grain they came out, plus each side's date range, timezone and filters.

  2. Match record by record

    River joins on the strongest key the two share, then finds what sits on one side and not the other.

  3. Classify and sum

    Every difference gets a class and a sign, and the classes are added up against the headline gap.

  4. Take a number in

    One number, the question it answers, and the residual you still cannot explain, said rather than buried.

What you get

  • One row per difference, each carrying a class, a sign and a count
  • A residual line that stays visible until it reaches zero or gets a name
  • The records present in one system and absent from the other, listed one by one
  • The share of the gap that is documented vendor behavior, quantified separately
  • One recommended number, with the exact question it answers stated beside it
  • The definition change that stops these two reports diverging again next month

Common questions

Which of the two numbers should I actually use?

Whichever one answers the question being asked, and the reconciliation makes that a stated choice rather than a guess. For paid spend efficiency, the platform's own click count is the number your invoice matches. For on-site behavior, sessions are. The output names the number, the question it answers, and the gap to expect between them. Three sources rather than two needs the attribution reconciliation instead.

Both our tools report active users. Why does that never match?

Because the phrase covers two different populations, and both vendors publish their definition. In Amplitude, an active user has logged at least one event in the interval. In GA4, an active user needs an engaged session, meaning ten seconds, a conversion, or two screens. Somebody who bounces in four seconds counts in one and not the other. A deck that publishes neither definition leaves the figure uncheckable.

What happens if the difference never reaches zero?

Then it is reported as a residual with a size, which is a finding rather than a failure. A residual inside the vendor's documented variance is expected noise, and it gets labelled that way. One above it means records are missing on one side, and those get listed individually. A variance that recurs every period belongs in a known conflict log.

Do the two exports need a shared key?

It helps, and it is not required. Given a key such as a gclid, an order ID or a ticket number, the reconciliation runs at the record level and names every unmatched row. Without one, River matches on the closest composite it can build from timestamps and dimensions, and says which part of the gap that leaves unresolvable.

Is this only useful for analytics tools?

No. The same shape covers a CRM's closed-won against what billing invoiced, a warehouse table against the dashboard reading from it, and a support view exported twice by two methods. Payer remittance against expected payment has its own remittance and denial triage, because the codes carry the reason. Otherwise the five classes hold, whatever the vendor.

Can it stop this happening again?

That is the second artifact. Every difference classified as a definition becomes a proposed wording change, with the reports it affects and the number it moves. A conflict that turned out to be documented vendor behavior is recorded as an expected variance instead, so next month nobody spends an afternoon rediscovering it. When both are CRM reports, the durable fix is a field dictionary naming who writes each field.

What do I walk into the meeting with?

One page. The number to quote and the question it answers, the gap split into explained and expected, the residual with its size, and one sentence naming the cause. The sheet sits behind it, so a challenge from the room is settled by opening a row. A dashboard description keeps it that way afterwards.

Reconcile Two Reports With Different Numbers

Fill in the form and your workspace opens with the work already underway.