Put lifecycle stage on the contact, and treat any company stage as a derived rollup you calculate from the contacts associated with it. The contact is the only object where lifecycle events actually happen (a form fill, an email click, a first order), so it is the only object that can hold a stage nobody has to guess at. The moment you let a company stage write back down to people, you stop measuring a funnel and start measuring an association graph.
Key takeaways
- Contacts own lifecycle stage. Companies carry a read-only rollup that is computed, never written back down.
- HubSpot's contact to company lifecycle sync follows the primary company association, so re-pointing that association silently restages people.
- Downward writes overwrite
hs_date_entered_*properties, which is what makes stage-to-stage conversion reports look impossible rather than merely wrong. - A contact funnel and a company funnel will never reconcile, because one company with six contacts is six rows in one report and one row in the other. Pick the denominator you report to the board and stop maintaining the other.
- Before you change any setting, run the property history audit. Most portals have three or four writers competing for the same field.
Which object should be the source of truth
The contact, in almost every ecommerce portal, including wholesale. Lifecycle stage is a claim about a relationship with a person: this human subscribed, this human was qualified, this human bought. Every native trigger HubSpot ships for stage progression fires on contact-level behaviour, and the default stage set documented in the HubSpot lifecycle stages reference (Subscriber, Lead, Marketing Qualified Lead, Sales Qualified Lead, Opportunity, Customer, Evangelist, Other) is written in the language of people, not organisations.
A company has no native event that says it became a Marketing Qualified Lead. Whatever value sits in the company's lifecyclestage field arrived there by inheritance from a contact, by a deal association, or by an import. That is the tell: if a field can only ever be derived, it should be stored as a derived field with a name that says so, not in the same property your marketing team filters on.
What the contact to company sync actually does
The lifecycle sync setting in your portal object settings propagates stage in both directions between a contact and its primary company, not all associated companies. That single word does most of the damage. HubSpot's record association documentation covers how a contact can be associated with many companies while exactly one holds the primary flag, and the associations API lets any integration change which one that is.
So the behaviour you get is: contact advances, primary company advances to match, and every other contact whose primary company is that record advances too. Change a contact's primary company (a support integration doing cleanup, a sales rep merging duplicates, a CSV import with a company domain column) and that contact inherits a stage from an organisation it may have joined last week.
Why downward writes corrupt the funnel
Take a wholesale account with four contacts: the buyer, someone in accounts payable, a warehouse manager, and a marketing coordinator who only ever subscribed to the newsletter. The buyer's deal closes won, the buyer moves to Customer (the mechanics of that automatic move are worth understanding on their own, and I covered them in why a contact turns Customer on deal creation). The sync pushes Customer up to the company. The company pushes Customer down to the other three.
You now have three problems that all look like reporting bugs and are not:
- Date stamps are fabricated. All four contacts get
hs_date_entered_customerset to the same second. Three of them never hadhs_date_entered_marketingqualifiedleadpopulated, so any stage-to-stage conversion report shows customers who skipped the middle of the funnel entirely. - Audience membership flips overnight. Active lists and enrolment criteria keyed on lifecycle stage swap their populations. A newsletter subscriber in accounts payable is suddenly excluded from acquisition sends and included in post-purchase ones.
- Regressions start firing. When another workflow tries to correct one of those contacts back to Subscriber, the sync pushes the lower stage up to the company and back down to everyone. That loop is the most common cause behind lifecycle stages moving backwards.
How to build a company rollup that stays coherent
Compute it on the company object with a custom property, and give it a name that cannot be confused with the native field. I use company_lifecycle_rollup as an enumeration with the same option labels, plus company_first_customer_date as a date. Both are written by company-object workflows that read associated contacts. Neither is ever written by a contact-object workflow, and neither writes back to contacts.
The rule that survives audit is furthest stage reached by any associated contact, evaluated against a fixed ordinal:
| Stage | Ordinal | Company meaning |
|---|---|---|
| Subscriber | 1 | Someone here reads our email |
| Lead | 2 | Someone here converted on content |
| Marketing Qualified Lead | 3 | Someone here met the scoring bar |
| Sales Qualified Lead | 4 | Sales accepted someone here |
| Opportunity | 5 | An open deal exists |
| Customer | 6 | The account has bought |
Two details make this hold up. First, store the ordinal mapping somewhere outside HubSpot (a table in your warehouse, a constant in whatever syncs your data) so the same ordering is used by every downstream report. Second, set company_first_customer_date from the earliest contact customer date, not the date the rollup last recalculated, or your cohort reporting will shift every time a workflow re-enrols.
If your tier supports association-based calculated properties, use those instead of workflows. Fewer moving parts, no enrolment limits, and no risk of a workflow pausing on a quota and leaving half the portal stale.
The audit that finds what is setting stage today
Do this before changing a single toggle, because most portals have three or four writers competing for the same field and turning off the sync without knowing the others just shifts the symptom. On a sample of twenty contacts that look wrong, open the property history for Lifecycle stage and read the source column on each change. You are sorting every write into one of five buckets: workflow, import, integration, API, or a human in the CRM.
Then confirm the sample against the portal-wide configuration:
- Object settings for lifecycle stages: the contact to company sync toggle, the set-to-Customer-on-deal-won toggle, and the backwards movement toggle.
- Every workflow that has Lifecycle stage as a set-property action, including ones on the deal and company objects, not only contact workflows.
- Integrations with write scope on contacts. Ecommerce and subscription apps frequently stamp Customer on sync, which means a refund or a test order can restage someone.
- Recent imports. An import that includes a lifecycle stage column will happily move thousands of records at once, and the history shows it as a single source.
Write down which writer you are keeping. Exactly one should own each transition. If you have just taken over the portal and none of this is documented, the wider sweep in auditing a HubSpot portal you have inherited covers the surrounding objects too.
Why the contact funnel and the company funnel never match
They are counting different units, and no amount of filter alignment fixes that. In the custom report builder, your choice of primary data source decides the grain of every row. Build the funnel on contacts and one wholesale account with six contacts contributes six rows. Build it on companies and it contributes one. Conversion rates computed from those two denominators will differ by a factor that tracks your average contacts per account, which for a wholesale-heavy store can be three or four.
Pick one as the reported number. For ecommerce with a direct-to-consumer majority, contact grain is the honest one, because that is also the grain your email programme sends at. For a wholesale-first business, report companies to the board and keep contacts as the operational view, but say so explicitly in the dashboard title so nobody reconciles two numbers that were never meant to agree. The same grain question drives which object should run your pipeline reporting, which I worked through in deal stage or lifecycle stage.
Where wholesale changes the answer
Only partially, and less than people expect. In a B2B setup, the buying unit genuinely is the organisation: Shopify's B2B model attaches orders to a company and its locations, with multiple contacts authorised to place them. So company-level revenue facts (first order date, reorder interval, credit terms) belong on the company, and you should store them there.
Stage is still different. You cannot email a company. Send eligibility, consent, engagement windows and suppression all resolve at the person level, so a lifecycle value that is only true of the organisation is worse than useless on a contact record: it actively misroutes sends. Keep the revenue facts on the company, keep the stage on the person, and join them in reporting rather than in the property layer.
Trade-offs and what I would ship
The case for turning the native sync on is that it costs nothing to configure and it is defensible in a portal where the contact-to-company ratio is close to one, nobody segments email on lifecycle stage, and the sales team wants a glanceable value on the company record. That describes a small number of stores. If yours is one of them, turn it on, document it, and accept that the first multi-contact account you land will break the pattern.
The case against a custom rollup is maintenance. It is another workflow or calculated property to monitor, it recalculates on association changes you do not control, and someone will eventually build a report on the native company field anyway because it is the one that autocompletes. Mitigate that by clearing the native company lifecyclestage to Other and naming your rollup so it sorts next to it in the property picker.
What I ship: contact owns lifecycle stage, native sync off, one custom company rollup computed as the furthest stage any associated contact has reached, company revenue dates stored separately from stage, and exactly one writer per transition documented in the portal. The rollup takes an afternoon. The alternative is a funnel report that nobody trusts and a quarterly argument about why the customer count went up in a week with no orders.