How to associate Stripe invoices with HubSpot companies, not just contacts
Every invoice that syncs from Stripe™ into HubSpot lands on a Contact. If you sell B2B, that's the wrong record. Revenue belongs to the account, not to whichever individual happened to be in the billing field — people change roles, leave companies, or were never the actual buyer. Ask "how much has this company paid us" and the honest answer, if you're relying on the native sync, is: open Stripe and add it up yourself.
Why it happens
Stripe doesn't have a "company." Stripe's billing model is built around the Customer object — an individual or billing entity with an email address, not a business hierarchy. There's no field on a Stripe customer that natively points at "this is a subsidiary of that parent account" the way a HubSpot Company record can imply. Any link between a Stripe customer and a HubSpot company has to be inferred or built — Stripe doesn't hand it to you.
HubSpot's native Data Sync only writes to the Contact object. HubSpot's invoice object can technically be associated with a Company through the API, but the built-in Stripe sync tool's own matching logic resolves a Stripe customer to a HubSpot Contact by email and stops there. It was never built to also resolve the contact's employer and attach the invoice to that Company record too.
Company matching is a real resolution problem, not a missing checkbox. The obvious approach — match on email domain — works until it doesn't: a bookkeeper billing from a personal Gmail address, an accounts-payable inbox on a different domain than the buyer's own, a reseller or accounting firm paying on behalf of several clients. Any automatic matcher needs a deliberate policy for these cases, or it will confidently attach revenue to the wrong account, or to no account at all.
What you can do about it today
1. Store a deterministic join key on both sides, starting now
Add a stripe_customer_id property to your HubSpot Company (and Contact) records. Even if you can't automate the matching today, capturing this key the moment you know it turns every future reconciliation from a fuzzy email guess into an exact lookup. This costs nothing and pays off every time you touch this problem again.
2. Reuse HubSpot's own Contact-to-Company association before building your own resolver
If the native Stripe sync has already matched an invoice to a Contact, that Contact very likely already has a primary Company associated with it — HubSpot associates contacts to companies by email domain automatically in most portals. Read the Contact's associated companies (GET /crm/v3/objects/contacts/{contactId}/associations/companies), take the one marked primary, and associate the invoice to that Company instead of deriving a domain match from scratch. This is more reliable than a fresh domain lookup, because it reuses an association HubSpot — or a human — has already curated, and it sidesteps the free-email-domain problem entirely, since it isn't matching the invoice's own domain at all.
3. Build a resolver on top of HubSpot's Associations API for what's left
For invoices with no existing Contact match, or Contacts with no primary Company, wire a webhook handler to Stripe's invoice.finalized or invoice.paid events, resolve the invoice's customer email to a domain, search HubSpot companies by that domain (POST /crm/v3/objects/companies/search, filtered on the domain property), and if you get a match, associate the invoice to it directly. HubSpot's invoice object (object type 0-53) supports company associations through the API — the gap is in the native sync's logic, not in what HubSpot's platform allows a marketplace app to do.
4. Maintain a personal-domain blocklist
Before trusting a domain match, exclude common consumer email providers — gmail.com, outlook.com, yahoo.com and similar. Without this, your resolver will happily create a "Gmail" company and start attaching real invoices to it.
5. Write the resolved company back into Stripe, so you only solve it once
Stripe customers carry a metadata field you can write to. Once you've resolved a Stripe customer to a HubSpot company — by domain match, contact lookup, or a manual decision — write the HubSpot company ID back into that customer's metadata. The next invoice from the same customer becomes an instant lookup instead of a repeated resolution.
6. Run it as a periodic spreadsheet match if you can't wire a live handler
Export Stripe customers and invoices (Dashboard → Customers, Dashboard → Billing → Invoices, or the list APIs) alongside an export of HubSpot companies with their domains. Match on domain in a spreadsheet, review the ambiguous rows by hand, and bulk-associate the resolved invoices using HubSpot's import tool, which supports associating imported records to existing ones by a matching property.
7. Give yourself a manual override, and write it down somewhere durable
Whatever method you use, some accounts will need a hand-set exception — a reseller paying on a client's behalf, a franchise billing centrally. Keep a simple table (even a spreadsheet) of stripe_customer_id → hubspot_company_id overrides that your matching process checks first, before falling back to the domain heuristic.
Where this breaks down
Domain matching is a heuristic, not a guarantee. It will silently misfire in both directions — merging unrelated accounts that happen to share a corporate domain, and failing to match accounts that pay from a personal or third-party address. Nobody finds out until someone notices a company's revenue total looks wrong.
Spreadsheet matching is a snapshot. The moment a new invoice is issued in Stripe, your join is out of date again. Most teams can sustain this quarterly at best, which means new invoices sit unassociated for weeks by default.
There's usually no real override mechanism. A one-off spreadsheet correction doesn't persist — next quarter, the same email resolves the same wrong way again unless someone remembers the exception and re-applies it by hand every time.
The "primary company" shortcut has its own edge cases. Not every Contact has a primary Company set, and in a portal with messy historical data, the primary association is sometimes wrong itself — inherited from an old job change, or set by whichever integration touched the record first. Treat it as a strong signal, not a guarantee, and keep the domain-based resolver as a fallback for when it's missing or looks implausible.
A correct association today says nothing about tomorrow. Even a perfectly matched invoice can drift later — a refund, a credit note, or a canceled subscription that the association step never revisits. Getting the invoice onto the right Company record is necessary, but it's a snapshot in time, not an ongoing guarantee that the number attached to it stays accurate. (More on that in our piece on refunds that still read "paid.")
Once invoices are reliably associated, HubSpot's calculated properties can sum a numeric property across associated records — so a running "total invoiced" figure on the Company becomes something HubSpot itself computes, instead of a number rebuilt in a spreadsheet every time someone asks.
What Trueness does about it
Trueness's free Drift Report already runs this resolution — matching every Stripe customer to a HubSpot Company and Contact — each time it builds your report, so you see exactly which Stripe subscriptions and invoices have no matching HubSpot record at all, priced in dollars. Writing the resolved match back into HubSpot as a permanent, associated record is on our roadmap; today, the report shows you the gap and you decide what to do about it.