Why do GA4 event counts differ between the UI, the Realtime API, and the Data API for the same date range?
Quick answer
The GA4 UI, the Realtime reporting surface, and the Data API each read from a slightly different processing stage and can apply different sampling or data-thresholding behavior depending on the query - so a difference between them, especially a small one, often reflects which layer you asked rather than a data problem. Exact parity is most likely once data has fully processed (past the 24-48 hour window) and the same date range, filters, and sampling settings are used on every query.
Why This Happens
Realtime reflects the live, largely unprocessed event stream and only covers roughly the last 30 minutes of activity by design - comparing a Realtime count to a standard report for 'today' compares two different data completeness states, not two measurements of the same thing.
The standard UI reports and the Data API can both apply high-cardinality sampling on very large, unsegmented queries (a GA4 property with enough volume) unless the property is on GA4 360 with guaranteed unsampled thresholds for certain report types, or unless the query is scoped narrowly enough to avoid it. The Data API and the UI can independently decide to sample the same underlying query differently depending on exact filters and dimensions requested, producing slightly different totals for what looks like an identical question.
A separate cause specific to the Data API: it's possible to build a request that quietly double-counts or under-counts relative to the UI by using the wrong metric (e.g. eventCount vs. eventCountPerUser) or by not applying the same date range boundary rules (the UI's calendar picker and the API's startDate/endDate strings can differ by a day if timezone handling isn't matched).
Fix It
Never compare Realtime to a standard/API report for the same period
Realtime is a different data completeness stage by definition - use it only to confirm an event is currently firing, not to reconcile totals against the UI or Data API.
Match the exact metric and dimensions between UI and API
If comparing the UI's 'Event count' card to a Data API call, confirm the API request uses the eventCount metric (not eventCountPerUser or a derived metric) and the same event-name filter, applied identically.
Check the property's sampling status for the specific report
In the UI, reports that hit a sampling threshold display a small sampling indicator/icon - if present, the UI number itself is an estimate, and comparing it to an unsampled Data API total for the same query will show a (usually small) discrepancy that isn't a bug.
Align timezone and date-range handling explicitly
Confirm the GA4 property's reporting timezone (GA4 Admin > Property Settings) matches the timezone assumption your Data API call is making for startDate/endDate - a one-day offset here is a common, purely mechanical source of mismatched totals.
How To Verify It Worked
Wait until a date range is fully outside the 24-48 hour processing window, then run the same query (same event name, same date range, no extra segmentation) through the UI's Reports and through the Data API's runReport method, using eventCount on both sides. A pass looks like matching totals (or a difference explained by a visible sampling indicator); a fail that persists after ruling out sampling and timezone differences is worth escalating as a genuine data issue.
If BigQuery export is linked, use it as a tiebreaker: SELECT COUNT(*) FROM `project.analytics_XXXXXXXXX.events_*` WHERE event_name = 'your_event' AND _TABLE_SUFFIX BETWEEN 'start' AND 'end' gives an unsampled, raw ground truth to compare both the UI and Data API numbers against.
Still Not Fixed?
We audit and fix GA4 implementations for a living - if this doesn't resolve it, the next step is usually a full event-by-event audit.
Talk to a GA4 Consultant