◈Home ▸Blog ◇Contact
Home/Blog/Why Two HubSpot Reports Count Contacts Differ…
How toHubSpotHubSpotReportingData Quality

Why Two HubSpot Reports Count Contacts Differently

10 min readBy Miloš Mitrović

When two HubSpot reports carrying the same filters return different contact totals, the filters are rarely the offender. The count diverges because the primary data source decides which records are eligible before a single filter runs, because a cross-object report counts rows instead of contacts, or because the date filter sits on a property that means something other than what its label suggests. Reconcile the definitions before you reconcile the numbers. The checklist at the end of this article isolates the cause in about twenty minutes without exporting your whole database.

Key takeaways

  • The primary data source is a filter you did not write. Adding a second object to a report quietly restricts the report to contacts that have an association to that object.
  • Cross-object reports return one row per association, so a contact on three deals is counted three times unless the measure is a distinct count of contact records.
  • Create date, First conversion date and Became a Customer Date answer different questions, and two of them get rewritten by automation.
  • Permission partitions and dashboard-level filters mean two people can open the same saved report and read different totals.
  • Duplicates inflate one number and merges deflate the other, so decide whether you are counting people or records before you argue about which report is correct.
  • For a recurring number that only needs a current total, a saved active list is the more trustworthy artefact. Reach for the report builder when the number needs history.

Which of the two numbers should you trust

Neither, until you can state in one sentence what each report counts. Most reporting disputes I get pulled into are definition disputes wearing a reporting costume: one report counts contacts created in a period, the other counts contacts that reached a stage in that period, and both are labelled "new customers" on the dashboard. Write the sentence out for each report, including the object being counted, the population it is drawn from, and the date property the range applies to. If the two sentences differ, you are finished. If they match, you have a genuine data problem and the rest of this article applies.

A useful forcing function: ask what a single row in each report represents. If you cannot answer that for a cross-object report, the total is not defensible, however confident the chart looks.

How the primary data source removes contacts before your filters run

The primary data source sets the base population, and every additional data source you attach narrows it. In the custom report builder you choose one primary source and can join related objects to it, and records from the joined objects only appear when they are associated with a record from the primary source (HubSpot, Create custom reports). The practical consequence is that a report built on Contacts plus Deals silently excludes every contact with no associated deal, even when no deal filter exists anywhere in the builder.

This is the single most common source of a gap between a single-object contact report and a "richer" report someone built later to add deal context. The richer report is not measuring the same population. It is measuring contacts that have at least one deal.

Report constructionPopulation actually counted
Single object, ContactsAll contacts matching the filters
Contacts primary, Deals joinedContacts with at least one associated deal
Deals primary, Contacts joinedContacts reachable from a deal, one row per deal association
Contacts primary, Marketing emails joinedContacts with at least one email event of the joined type

Before you change anything, strip the report back to a single data source and re-run it. If the number now matches the other report, you have found your answer and the fix is a filter, not a rebuild.

Why a cross-object report counts the same contact several times

A cross-object report returns one row per association, and a naive count measure counts rows. A contact associated with three deals contributes three rows. If the measure is a count of deals, that is correct. If the chart is titled "contacts" and the measure is a plain count, the total is inflated by exactly your association density, which in a B2B portal with repeat business can be a factor of two or more.

The fix is to set the measure to a distinct count of the contact record, not a count of rows, and to check any secondary breakdown for the same problem. A breakdown by deal stage on a contact count will duplicate any contact sitting on deals in two stages, so the segments will sum to more than the total. When the segments do not sum to the whole, you are looking at row duplication, not a rounding artefact.

The same duplication shows up at the company level when lifecycle data is recorded on both the contact and the company. If you have not settled which object owns that field, the report will keep drifting. I covered that decision in should the contact or company own lifecycle stage.

The date property that is not the date you meant

A date range is only as meaningful as the property it filters. HubSpot ships several contact date properties that read similarly and answer different questions: Create date, First conversion date, Recent conversion date, Became a Marketing Qualified Lead Date, Became a Customer Date and Last activity date are all distinct fields with distinct write behaviour (HubSpot, default contact properties). Two reports with a "last 90 days" range on different properties will never agree, and neither is wrong.

Three specific traps are worth checking by name:

  • Cross-object date ambiguity. In a Contacts plus Deals report, the date picker may be filtering on the deal create date while you read the chart as contact creation. Open the filter and confirm which object the property belongs to.
  • Stage dates that get rewritten. Stage timestamp properties are set by automation, and when a record moves backwards through lifecycle stages those timestamps can be cleared or reset, which changes historical totals retroactively. If your "customers created in March" number moves every month, start with lifecycle stages moving backwards.
  • Time zone. Date ranges are evaluated against the account time zone set in your account defaults (HubSpot, manage your account defaults). A US portal set to UTC will place late-evening activity on the following day, which moves records across month boundaries and puts a consistent few percent between HubSpot and any system reporting in local time. That same mismatch is behind most platform-versus-platform disputes, including the one described in HubSpot versus Google Ads attribution.

Filter logic that reads the same and evaluates differently

Two filter sets can look identical in a screenshot and evaluate differently because of grouping and null handling. Check these four things in order:

  1. AND and OR grouping. Filters inside one group combine with AND, separate groups combine with OR. Moving a single criterion into its own group widens the population dramatically. Compare group structure, not the list of criteria.
  2. Blank values. A filter such as "country is not United States" typically excludes records where country is empty, because an empty property does not satisfy a value comparison. If one report uses a negative filter and the other uses a positive one, the delta is usually your unknowns.
  3. Renamed dropdown options. Dropdown properties store an internal value separate from the label. Rename the label and older filters can keep matching the old internal value, so two reports built months apart target different underlying values while displaying the same word.
  4. Marketing contact status. In portals on the marketing contacts model, some reports and most email tooling are scoped to marketing contacts while a CRM report counts every contact record. That distinction alone can account for the majority of a gap.

Why the same saved report shows two people different totals

Because record access is per user, and dashboard filters are applied on top of what the report itself defines. If your portal uses teams or permission partitioning, a user only counts the records they are allowed to see, so a manager and a rep open the same saved report and read different numbers with no configuration difference between them. Before escalating a discrepancy, have both people confirm the count from the same login, or export and compare.

Dashboard-level filters are the second half of this. A filter applied at the dashboard level narrows every report on it for that viewing session, and the report opened directly from the report library will not carry it. When someone screenshots a number from a dashboard and someone else quotes the number from the report, a mismatch is expected behaviour rather than a fault.

If you inherited the portal and do not know which partitions exist, that audit comes first. I walk through the sequence in auditing a HubSpot portal you have just inherited.

Duplicates, merges and the identity you are counting

Decide whether you are counting people or records, because HubSpot counts records. Two contact records for the same person, created from a checkout email and a support form, count twice in every report until they are merged, and merging keeps one record while the secondary record's history is folded into it (HubSpot, merge records). A report run before a merge sweep and a report run after it will disagree, permanently, about a historical period.

This matters most for ecommerce portals fed from a store platform, where guest checkouts and normalised email casing create duplicates at a steady rate. The store side of the same problem is covered in finding and merging duplicate Shopify customers. Fix identity at the source before you accept any historical contact count as final.

The reconciliation checklist

Work through this in order and stop at the first step that explains the gap. It is deliberately ordered by how often each cause is the culprit.

  1. Write the one-sentence definition of each report. Object, population, date property. Most disputes end here.
  2. Count the data sources in each report. Strip the multi-source report to a single source and re-run.
  3. Check the measure. Distinct count of contact records, or count of rows?
  4. Open both date filters and read the object and property name, not the label on the chart.
  5. Compare filter group structure, not filter lists, and look specifically for negative filters and blank handling.
  6. Confirm both numbers were read by the same user, from the report itself rather than a filtered dashboard.
  7. Export both record sets with the record ID column and diff the IDs. The delta set is the answer, and reading twenty of those records tells you more than another hour in the builder.
  8. Scan the delta for duplicates on the same email domain or name before concluding either report is broken.

Step seven is the one people skip and the one that ends the argument. A list of the specific records present in one report and absent from the other converts a philosophical disagreement into a five minute inspection.

Trade-offs and what I would do

Not every number deserves a custom report. If the metric is a current total with no history requirement (active customers, contacts in a stage, everyone eligible for a campaign), a saved active list is the more trustworthy artefact. List membership updates automatically as records change, the criteria sit in one visible place, and there is no primary data source or measure to get wrong (HubSpot, create active or static lists). The cost is that a list tells you what is true now and nothing about what was true in March.

Use the report builder when you need the time series, a breakdown, or a measure other than a count. Accept that you are then responsible for the data source, the measure and the date property, and that those three choices need to be documented next to the report rather than living in the head of whoever built it. My rule with client portals is that any number quoted in a board deck carries a written definition in a shared doc, and that definition names the property, not the chart title.

The harder judgement call is when to stop reconciling. If two reports disagree by less than a couple of percent and the delta is duplicates or time zone boundary records, fix the underlying cause and move on rather than chasing exact agreement, because both numbers are directionally sound. If the gap is more than five percent, stop using both until it is explained, since a gap that size usually means one report is answering a question nobody asked. And when the question you want cannot be expressed in the builder at all, that is a modelling problem rather than a reporting one, which is the case I made in the lead quality report HubSpot cannot build for you. At sufficient volume the honest answer is to move the calculation out of the CRM entirely, for the reasons set out in retention reporting past a million orders.

Sources

M
Miloš Mitrović
Email Marketing for Ecommerce

Have a question or a project?

Whether it is about this post or a system you want built, I'm happy to talk.

Get in touch

404

Post not found. It may have been moved or the link is incorrect.

← Back to the blog
Summarize with AI
ChatGPT, Perplexity, and Grok open with the prompt ready to run. Claude, Gemini, and Copilot open a chat with the prompt copied; press Ctrl+V (Cmd+V on Mac) to paste. The full text is included, so it works even without web access.