The first version of every Stripe-to-CRM integration is a webhook. A backend engineer writes a handler that listens for customer.subscription.updated, looks up the contact in HubSpot or Salesforce, and patches a few fields. It works on day one. It works for a quarter. By month six, the support team is opening tickets because contacts in the CRM say "Free" when Stripe says "Team," and someone has to write a reconciliation job. This is the standard arc for any team trying to sync Stripe to CRM without a real sync engine, and it has nothing to do with how good the engineer is.
The reason it breaks is simple. Stripe's billing object graph is bigger than most webhook handlers acknowledge, the CRM expects records with stable identifiers and clean field types, and the network between them is unreliable. A serious Stripe-to-CRM sync has to handle all five of the major Stripe objects, has to map them onto the CRM's data model, and has to recover from missed events without manual intervention. Most teams figure that out the hard way.
This article is the long version of what we wish someone had handed us before we wrote that first webhook. It walks through the real Stripe object graph, the right way to map each object onto a CRM, and the three architectural patterns teams actually use in production. There is some Oneprofile in the last section because we built a tool for this. Most of the article applies whether you use us or not.
Why most attempts to sync Stripe to CRM break after six months
Webhooks feel like the obvious answer because Stripe's API documentation leans heavily on them. Subscribe to events, react to events, write the result somewhere. The pattern is clean on a whiteboard. The problems show up in production.
The first is missed events. Stripe retries failed webhook deliveries for up to three days, but if your handler is down for a week (a holiday outage, a deploy gone wrong, a quota exhausted), the events are gone. There is no replay. You have to backfill from the API, which means writing a second code path that reads the Stripe API directly, paginates through subscriptions, and fixes whatever drifted. Now you have two integrations to maintain.
The second is event ordering. Stripe webhooks are not strictly ordered. A customer.subscription.updated event can arrive before the customer.created event for the same customer, especially under retry pressure. If your handler tries to update a contact that does not exist yet, it either fails or creates a duplicate. Most teams discover this when the duplicates pile up in HubSpot and someone gets paged.
The third is schema drift. Stripe adds fields. The CRM adds fields. The product team renames a price tier. Without a sync layer that re-discovers the source schema and tracks property changes on the destination, the integration silently stops syncing the new fields. Sales does not notice for two months. Then someone closes a deal on a plan the CRM does not know exists.
These failures compound. By the time a team writes its third reconciliation script, most teams admit they are not really doing webhook sync anymore. They are doing nightly batch sync with webhooks as an optimization, and they are doing both poorly.
What to sync from Stripe: customers, subscriptions, invoices, products, prices
Most Stripe-to-CRM tutorials stop at customers and one-off charges. That is fine for a side project. For a real revops setup, you need the full billing object graph.
Here is the practical minimum:
Customers. The Stripe customer is the join key for everything else. It carries email, name, default payment method, and metadata. Every other object hangs off the customer ID.
Subscriptions. This is where plan, status (
active,trialing,past_due,canceled), current period, and trial end date live. Subscriptions are the source of truth for whether a customer is paying you and what they are paying for.Invoices. Invoices show what was actually billed and whether it was paid. They give support a clean answer when a customer asks "did my last payment go through" without anyone opening Stripe.
Products. The list of things you sell. If your sales team talks about "Pro plan" or "the Enterprise tier," those names live in Stripe products. Without a product catalog sync, the CRM has no consistent vocabulary for what a customer is on.
Prices. A price is a product plus a billing interval and currency. Subscription items reference prices, not products directly. If you skip prices, the CRM cannot tell a $20/month seat from a $200/year seat. Both look like the same product to a sales rep filtering by plan.
Coupons, tax rates, and payment methods are nice-to-haves. Skip them in v1. The five objects above cover roughly 95% of what sales, support, and lifecycle teams ask the CRM about.
There is a sixth object worth a mention: subscription items. Most subscriptions have one item, but tiered or seat-based plans have several. If your business sells per-seat pricing, the item count is what changed when MRR went up. Either sync items as a separate object or roll them up into a JSON field on the subscription. Both work; the choice depends on how your CRM models products.
How to map objects when you sync Stripe to CRM
Mapping is where most integrations get sloppy. The standard CRM data model has Contacts, Companies, Deals, and Products. Stripe's data model has Customers, Subscriptions, Invoices, Products, and Prices. The translation is not one-to-one, and it matters.
Here is a mapping that works for most B2B SaaS companies:
Stripe object | CRM record | Key fields to push |
|---|---|---|
Customer | Contact (B2C) or Company (B2B) | Stripe customer ID, billing email, default payment method, lifetime value |
Subscription | Custom object or Company fields | Plan name, status, MRR, current period end, trial end, cancel-at-period-end |
Invoice | Custom object or activity | Number, amount due, amount paid, status, hosted invoice URL, due date |
Product | Product | Name, description, active flag, internal product code |
Price | Price book entry or product variant | Unit amount, currency, billing interval, product reference |
A few choices in the table deserve a word. For B2B, the Stripe customer almost always represents an account, not a person, so it should map to a CRM company rather than a contact. The billing email goes on the company as a billing contact, not as the primary email of a salesperson's contact record. Mixing those two is the source of most "why did the renewal email go to the wrong person" tickets.
Subscriptions are the trickiest mapping because most CRMs do not have a native subscription object. You have three choices: roll the subscription into fields on the company (cleanest if you only have one subscription per company), create a custom object for subscriptions (best for teams with multiple subscriptions per account), or model subscriptions as deals with a recurring stage (works if your sales process already revolves around deals). Pick one and stick with it. Mixing approaches across the team is worse than picking the wrong one.
Invoices almost never need to be a first-class CRM object. Push the most recent invoice's status and URL onto the company, and let support click through to Stripe for history. Syncing every invoice as a separate record creates a lot of noise without much payoff.
For a stripe hubspot sync setup specifically, HubSpot's custom object documentation covers the modeling work, and the Stripe customer-to-company mapping is the path of least resistance. For a stripe salesforce integration, the Account is the natural home, and the standard Products and Pricebook objects do most of the work for the product catalog. If you sync Stripe subscriptions as a custom object in either CRM, give the object a stable external ID field that holds the Stripe subscription ID. That one decision saves a year of deduplication pain.
Sync Stripe to CRM, then fan out to sales, support, and lifecycle tools
The CRM is rarely the only destination. Once you have stripe billing data in CRM, the same fields tend to leak into other tools.
Sales wants plan and MRR on every contact in their outbound tool so they can prioritize expansion accounts. Support wants invoice status and last payment date in the help desk so the agent does not have to switch tabs. Lifecycle marketing wants trialing versus active versus canceled so the upgrade emails do not go to people who already upgraded. That last one is the same stale-data failure that batch sync tends to cause everywhere else.
The shape of the problem is the same in all four tools. Each one needs a small subset of Stripe billing fields refreshed regularly. The difference is the API and the field model. If your team builds a separate webhook handler for each tool, you are writing the same integration four times with different SDKs.
The pattern that works is to treat Stripe as one source and fan out. A single sync engine reads from Stripe, normalizes the customer and subscription state, and writes the relevant subset of fields into each destination tool. Adding the fifth tool is a config change, not another sprint.
This is also the cleanest way to handle the timing question. Sales and support need fresh data, where a five-minute lag is fine and an hour is not. Marketing automation can tolerate longer windows because most lifecycle emails are not minute-sensitive. A central sync engine can run different cadences per destination without forcing the slowest tool's tolerance on everyone.
When to sync Stripe to CRM directly vs. through a warehouse
The other axis is architectural. There are three patterns for getting Stripe data into CRMs and adjacent tools, and the right choice depends on team size, billing complexity, and whether you already have a data team.
Pattern | When it fits | What you give up |
|---|---|---|
Webhooks plus reconciliation | <1k events/day, one destination, an engineer who owns it | Fragile at scale, every new destination is a new project |
Direct sync platform | SMB and mid-market, 2-10 destinations, no warehouse | Less control over historical analytics |
Warehouse-based reverse ETL | Existing Snowflake/BigQuery, finance reconciliation needed, data team in place | Cost, complexity, weeks to first sync |
Webhooks plus reconciliation is the path most teams start on, and it is fine for the first year if you only need Stripe in one CRM and you have an engineer willing to own the codebase. It stops being fine when the destination count grows or when the engineer leaves.
A direct sync platform sits in the middle. It talks to the Stripe API and the CRM API on a schedule, with change tracking and retries built in. This is the right answer for most teams between 10 and 200 people. You get fresh data in operational tools without standing up a warehouse, and the cost is predictable. The trade-off is that you do not get a queryable history of every billing event in one place. If you need that, a warehouse is the right answer regardless.
Warehouse-based reverse ETL is the third pattern. Stripe lands in Snowflake or BigQuery first, then a tool like Hightouch or Census pushes it back out to the CRM. This is overkill for operational sync, but it is the right pattern when you already have a warehouse and a data team for other reasons. The marginal cost of adding Stripe-to-CRM sync to an existing warehouse is low. Standing up a warehouse just to do this is a year-long project.
The honest version: most teams do not need a warehouse for billing-to-CRM sync. They need a sync engine that handles the five Stripe objects, the CRM data model quirks, and the operational fan-out to other tools. If you already have a warehouse for analytics, layer reverse ETL on top of it. If you do not, do not build one for this.
What we built
We built Oneprofile because we kept watching teams hit the same wall on stripe product catalog sync, subscription state, and invoice fan-out. Oneprofile syncs the full Stripe billing object graph (customers, subscriptions, invoices, products, and prices) bidirectionally into HubSpot, Salesforce, Attio, and the rest of the SaaS tools that need billing context. No webhooks to maintain, no warehouse staging, no nightly reconciliation cron job. Connect Stripe, pick your destinations, map fields, and the data flows.
It is also free to start. Most teams are syncing real billing data into their CRM the same afternoon. If you have been running a webhook handler for the last two years and want to retire it, that is the conversation we have most often.
A note on what we are not. If you need a queryable, append-only history of every Stripe event for finance reconciliation against the general ledger, that is a warehouse problem, and we are not the right tool. We are the right tool when sales, support, and lifecycle teams need current billing state in the systems they already work in.
The closing thought is just this. Stop treating billing-to-CRM sync as five disconnected webhook handlers. It is one problem with one shape: fan out a small set of fields from Stripe to wherever a human looks at customer data. It should be solved once.
What Stripe objects should I sync into my CRM?
Is webhook-based Stripe sync reliable enough for a CRM?
Do I need a data warehouse to sync Stripe to my CRM?
How do Stripe products and prices map to CRM records?
Can I sync Stripe to HubSpot and Salesforce at the same time?
