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]])| Lifecycle stage | Stripe records | Spend | Segment | |
|---|---|---|---|---|
| ana@lune.fr | Customer | 1 | 1440 | Paying |
| ben@hart.co.uk | Lead | 0 | 0 | Never paid |
| eva@nordic.dk | Lead | 2 | 690 | Paying — 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
- Put each export on its own tab and format both as tables (HubSpot, Stripe).
- Add the Email key column to both tables with LOWER and TRIM.
- In the HubSpot table, add Stripe records and Spend with COUNTIFS and SUMIFS on the key.
- Add a Segment column that compares lifecycle stage with spend.
- 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
| Lifecycle stage | Spend | Segment | Action | |
|---|---|---|---|---|
| ana@lune.fr | Customer | 1440 | Paying | None |
| ben@hart.co.uk | Lead | 0 | Never paid | Keep in nurture |
| eva@nordic.dk | Lead | 690 | Paying — stage out of date | Set stage to Customer |
| joe@kite.io | Customer | 0 | Customer stage, no payment | Check 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
| Symptom | Likely cause | Fix |
|---|---|---|
| Known customer shows Never paid | Paid with a different email, often a finance or personal address | Match the remainder on company name or domain and review by hand |
| Spend looks doubled | Customer and payment exports both summed | Use one spend source only |
| Spend includes refunded amounts | Gross payments summed | Subtract refunds, or use a net figure if your export has one |
| Emails look equal but don't match | Non-breaking spaces or trailing dots pasted in | Add 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.