Analistable

How to match HubSpot contacts to Stripe customers

By the Analistable team · Updated · 6 min read

Export contacts from HubSpot and customers (with total spend) from Stripe, then match on email, lower-cased and trimmed on both sides. Because one person can have several Stripe customer records, sum Stripe spend per email before joining. You'll get three lists: paying contacts, contacts who never paid, and Stripe customers missing from HubSpot.

Part of our guide: How to join spreadsheets on a common column

Normalise the email

Email key =LOWER(TRIM([@Email]))

Add this column to both exports. Leave plus-addressing (ana+shop@…) as it is unless you know the same person uses both forms — merging those can link different accounts.

Aggregate Stripe first, then join

Spend         =SUMIFS(Stripe[Total spend], Stripe[Email key], [@[Email key]])
Stripe records =COUNTIFS(Stripe[Email key], [@[Email key]])
HubSpot contacts with Stripe spend
EmailLifecycle stageStripe recordsSpendSegment
ana@lune.frCustomer11440Paying
ben@hart.co.ukLead00Never paid
eva@nordic.dkLead2690Paying — stage out of date

Eva is still a Lead in HubSpot but has paid through two Stripe customer records — update her lifecycle stage, and merge the duplicate Stripe customers if appropriate.

Stripe customers missing from HubSpot

=FILTER(Stripe[Email key], COUNTIF(HubSpot[Email key], Stripe[Email key]) = 0, "None")

These customers bought without ever being recorded in the CRM — often self-serve sign-ups or checkout pages that don't sync.

Privacy

Both exports contain personal data. Work on them locally rather than uploading them to a third-party tool, and delete the working files when you're done. Analistable processes the files in your browser, so the rows don't leave your computer.

Matching deals rather than contacts? See matching CRM deals to invoices.

Write the results back

To update HubSpot, export the matched list with the email and the properties you want to set (lifecycle stage, total spend) and import it as an update to existing contacts, matched by email. Do a small test import first, and keep the original export as a backup.

Business accounts

For B2B, one company often pays through one Stripe customer while several people are HubSpot contacts. Match at company level as well: take the domain from each email and compare it with the domain of the company's billing email.

What to export

From HubSpot, export contacts with email, lifecycle stage, company, owner and create date. From Stripe, export customers with email, customer ID, created date and a spend figure; field names and which spend totals are available vary with the export and your Stripe settings, so check the headers before building formulas. If the customer export lacks a spend column, export payments instead and sum successful payment amounts per customer email.

Keep currency in mind: if you sell in more than one currency, sum spend per email and currency, or convert to one currency before comparing segments.

Step by step in Excel and Google Sheets

  1. Put each export on its own tab and format both as tables (HubSpot, Stripe).
  2. Add the Email key column to both tables with LOWER and TRIM.
  3. In the HubSpot table, add Stripe records and Spend with COUNTIFS and SUMIFS on the key.
  4. Add a Segment column that compares lifecycle stage with spend.
  5. On a new tab, list Stripe emails missing from HubSpot with FILTER (Excel 365 or Google Sheets).
Segment =IF([@Spend] = 0,
             IF([@[Lifecycle stage]] = "Customer", "Customer stage, no payment", "Never paid"),
             IF([@[Lifecycle stage]] = "Customer", "Paying", "Paying — stage out of date"))

In Excel 2019, FILTER isn't available: add a column to the Stripe table with =COUNTIF(HubSpot[Email key], [@[Email key]]) = 0 and filter it to TRUE instead. COUNTIFS, SUMIFS, LOWER and TRIM work in every version and in Google Sheets.

Second example: the four segments

Contacts after the join
EmailLifecycle stageSpendSegmentAction
ana@lune.frCustomer1440PayingNone
ben@hart.co.ukLead0Never paidKeep in nurture
eva@nordic.dkLead690Paying — stage out of dateSet stage to Customer
joe@kite.ioCustomer0Customer stage, no paymentCheck for an invoice paid outside Stripe or a cancelled trial

The fourth segment is easy to miss: contacts marked as customers with no payment at all. Some paid by bank transfer or through another system, so check before changing their stage; others are trials that never converted.

Troubleshooting

Common mismatches
SymptomLikely causeFix
Known customer shows Never paidPaid with a different email, often a finance or personal addressMatch the remainder on company name or domain and review by hand
Spend looks doubledCustomer and payment exports both summedUse one spend source only
Spend includes refunded amountsGross payments summedSubtract refunds, or use a net figure if your export has one
Emails look equal but don't matchNon-breaking spaces or trailing dots pasted inAdd SUBSTITUTE for CHAR(160) to the key formula

Keeping it current

Run the match monthly with fresh exports and keep the formulas in place, so only the data changes. To check the result, the count of Stripe emails matched plus the count missing from HubSpot should equal the number of distinct Stripe email keys, and total Spend across the HubSpot table plus spend of the missing customers should equal the Stripe total for the period. For the general method, see joining two spreadsheets on a common column and matching CRM deals to invoices.

Doing the same join in SQL or Power Query

For large exports, or if you'd rather not maintain formulas, aggregate Stripe and left-join it to HubSpot in one query. This runs as written in DuckDB and SQLite:

WITH stripe_spend AS (
  SELECT LOWER(TRIM(email)) AS email_key,
         COUNT(*)           AS stripe_records,
         SUM(total_spend)   AS spend
  FROM stripe
  GROUP BY 1
)
SELECT h.email, h.lifecycle_stage,
       COALESCE(s.stripe_records, 0) AS stripe_records,
       COALESCE(s.spend, 0)          AS spend
FROM hubspot h
LEFT JOIN stripe_spend s ON s.email_key = LOWER(TRIM(h.email));

In Power Query, add the lower-cased key to both queries, use Group By on the Stripe query to sum spend per key, then Merge Queries with a left outer join from HubSpot. Grouping before merging is the step people skip, and it's why spend appears duplicated when one email has several Stripe customers.

Common mistakes

  • Joining before aggregating, which repeats the HubSpot contact once per Stripe record.
  • Updating lifecycle stages in bulk without checking the Customer stage, no payment group first.
  • Importing spend back into HubSpot without saying which date range it covers.

Checking the query output

Run on the three contacts in the table above, the query returns ana@lune.fr with 1 record and 1,440, ben@hart.co.uk with 0 and 0, and eva@nordic.dk with 2 records and 690 — the same figures as the SUMIFS and COUNTIFS columns, which is a quick way to confirm both methods agree.

Frequently asked questions

How do I match HubSpot and Stripe data?
Export both, lower-case and trim the email column, sum Stripe spend per email, and join to HubSpot contacts on that email.
Why do some emails have several Stripe customers?
A new Stripe customer record is often created per checkout. Sum them per email before joining.
How do I find paying customers missing from HubSpot?
Filter the Stripe emails whose COUNTIF in the HubSpot list is zero.
Is it OK to export customer data for this?
Only within your privacy policy and data protection rules. Keep the files local and delete working copies when you're done.
What if a customer changed their email?
They'll appear unmatched on both sides. Review unmatched pairs with the same name or company, and record the old and new email in your mapping.
Can I match on Stripe customer ID instead of email?
Only if the Stripe customer ID is stored on the HubSpot contact, for example through an integration or custom property. Otherwise email is the shared field.
Should plus-addressed emails be merged?
Not by default. Treat ana+shop@ and ana@ as different keys unless you have confirmed they belong to the same person.

Related guides