Every HubSpot to Salesforce migration looks simple on the sales call and messy in week three. The records move fine. It's the relationships between them, the workflows that quietly run the business, and the data nobody remembers is stale that cause the rework. Here's what actually has to happen, in the order it has to happen β plus what we found when we tested HubSpot's API directly instead of trusting the docs.
Why companies move from HubSpot to Salesforce
Most teams don't migrate because HubSpot is bad β they migrate because they've outgrown it. The pattern we see most often:
- Sales complexity has outgrown the pipeline. Multi-step approval processes, territory rules, complex quoting, or CPQ needs that HubSpot's deal pipeline wasn't built for.
- A parent company or investor standardizes on Salesforce. Common after an acquisition or a funding round with reporting requirements across a portfolio.
- Cross-department needs outgrow a sales-and-marketing tool. Finance, support, and operations all need to work off the same record, and HubSpot's object model wasn't designed for that breadth.
- The AppExchange ecosystem matters. Industry-specific packages (nonprofit, real estate, field service) exist for Salesforce and don't for HubSpot.
None of these make HubSpot the "wrong" tool β they just mean the org has changed shape since the HubSpot decision was made.
What actually has to move
Break it into buckets before touching any migration tool. Each one has a different level of difficulty:
| Data type | Typical difficulty | What to watch for |
|---|---|---|
| Contacts & Companies | LowβMedium | Duplicate contacts, and deciding which contacts become Salesforce Leads vs. Contacts |
| Deals β Opportunities | MediumβHigh | Multiple pipelines, each with its own custom stages; deal stage history often doesn't migrate at all |
| Owners β Users | Medium | Every HubSpot owner needs a matching, active Salesforce User before records load |
| Custom properties | MediumβHigh | HubSpot field types (dropdown, checkbox) need explicit Salesforce field-type mapping, and the fields must exist first |
| Associations | High | Relationships live in a separate HubSpot API, not on the record, and can carry more than one association type |
| Activity history (notes, emails, calls, meetings, tasks) | High | Each type is its own object and its own set of API calls; volume per record can run into the hundreds |
| Workflows & automation | High | Doesn't migrate at all β has to be rebuilt as Salesforce Flow (see below) |
Three ways to run the migration
1. Manual CSV export/import
Export HubSpot objects to CSV, clean them in a spreadsheet, and import with Salesforce Data Loader. Workable for a small dataset with simple relationships β under a few thousand records, minimal associations, one pipeline. Breaks down fast once you need to preserve which contact belongs to which deal, bring activity history across, or keep both systems live during a transition.
2. Native and AppExchange migration tools
Purpose-built migration and sync tools handle field mapping and object relationships with less manual reconciliation than raw CSVs. Faster to set up than a custom build, but still needs someone who understands both data models to configure the mapping correctly β a wrong default mapping migrates cleanly and is wrong.
3. Custom scripted migration
For larger datasets, multiple pipelines, activity history, or a required parallel-run period where HubSpot and Salesforce need to stay in sync, a custom script gives full control over transformation logic, deduplication, and error handling. This is the approach we run in-house: a Node.js pipeline that extracts from the HubSpot API, transforms and validates the data, maps it against a documented object/field table, and loads it into Salesforce through the Bulk API β built to handle the edge cases (merged duplicates, per-pipeline stage mapping, multi-type associations) that a CSV export can't.
Every batch load is an upsert keyed on the stored HubSpot ID β a failed or re-run job picks up where it stopped instead of creating duplicates or starting the whole dataset over.
Mapping HubSpot objects to Salesforce
The core mapping looks close to 1:1 on paper. In practice, three of these rows hide a decision you have to make up front:
| HubSpot | Salesforce | The decision hiding in it |
|---|---|---|
| Contact | Lead or Contact | Route by lifecycle stage. Early-stage records usually become Leads; customers become Contacts. |
| Company | Account | Which company is primary when a contact is linked to several. Extras can use Contacts to Multiple Accounts. |
| Deal | Opportunity | One stage-mapping table per pipeline, not one for the whole account. Pipeline itself often becomes a Record Type. |
| Owner | User | Owner-to-User lookup table; plan what happens to records owned by people who have left. |
| Ticket | Case | Only if Service Hub is in use. Status and priority need their own mapping; SLA logic is the hard part. |
| Note / Email / Call / Meeting / Task | Note / EmailMessage / Task / Event / Task | How much history to bring, and whether call duration and outcome need custom fields. |
| Custom objects & properties | Custom objects & fields | Every custom property needs a Salesforce field created before loading. Nothing here migrates automatically. |
What we found testing the HubSpot API
Before writing a line of migration code, we set up a HubSpot sandbox, created a Private App token, and called each endpoint directly to see what the real responses look like. Documentation tells you what's possible. The raw responses told us what would actually break a script. These are the findings that shaped how we build every HubSpot migration now.
Relationships aren't on the record
A HubSpot contact's company isn't a field on the contact. It lives in a separate Associations API (/crm/v4/associations), which returns only the related record's ID. Getting the actual data takes another call, and associations paginate on their own, separately from the records.
What it means: far more API calls than record counts suggest. Batch sizing and runtime estimates have to account for it.
One relationship can come back with two labels
A single contact-to-company link returned two association types: HubSpot's built-in "Primary" type and a custom type whose label came back empty. Resolving the custom label takes a separate lookup.
What it means: set the Salesforce lookup from the Primary association specifically. Pick the wrong one and contacts attach to the wrong account.
Multiple pipelines, each with its own stages
One sandbox we tested had five separate deal pipelines β a sales pipeline plus several fundraising pipelines β each with completely different, custom-named stages. This is common in nonprofit and multi-product orgs.
What it means: one global stage mapping won't work. Every pipeline needs its own table, built from the client's real configuration.
Stage IDs aren't reliable β labels are
Some stage IDs are readable (closedwon), others are opaque numbers like 4237784824. Deals return the ID, not the label you see in HubSpot. The good news: every stage also carries "is closed" and "probability" values that map straight onto Salesforce's own stage settings.
What it means: pull the pipeline definitions first and map by label. Never hard-code stage IDs.
Some "closed" stages aren't Closed Won
Two pipelines had a closed, 100%-probability stage with its own name, separate from Closed Won. It looks like a win to a script. It may mean something different to the business.
What it means: any custom closed stage needs a business owner to confirm its meaning before it's mapped, or win-rate reporting will be off.
Activity history is five separate pulls
Notes, emails, calls, meetings, and tasks are each their own object. None of them come back inside the contact or deal response β each type is fetched separately and linked through associations.
What it means: an active deal can carry hundreds of logged activities. Estimate runtime on activity volume, not just record count.
Search has a much tighter rate limit
HubSpot's standard CRM endpoints allow a generous request rate, but the Search API has its own far lower limit, shared across every object type. A 429 response tells you when you've hit it.
What it means: extract with the list endpoints and cursor pagination, and keep search for targeted lookups. Build in backoff from day one.
A missing token scope fails quietly
HubSpot retired API keys; access now runs through a Private App token with scopes you choose per object. Leave one out and only that object type fails, which is easy to miss in a test run that mostly works.
What it means: check the token's scopes against every object in play before the production run, and compare record counts after it.
Hidden pitfalls that surface mid-migration
Some problems don't show up in the planning phase β they show up once real data starts hitting Salesforce. These are the ones that most often catch teams off guard.
Data architecture surprises
- Association model mismatch. HubSpot lets a contact associate loosely with multiple companies, deals, and tickets at once. Salesforce expects a contact to have one primary Account; extra company links need Contacts to Multiple Accounts or a junction object. Skipping that decision is how records end up orphaned or duplicated.
- Custom objects and IDs need explicit translation. Standard properties (name, email, phone) map cleanly. Custom objects and any HubSpot-specific unique identifiers don't β each one needs a deliberate mapping decision, or the records land in Salesforce with no meaningful relationship to anything else.
- Skipping ID preservation breaks the audit trail. If the original HubSpot record ID isn't stored in an external ID field on the Salesforce record, you lose the ability to trace a record back to its source, re-run a failed batch without creating duplicates, or roll back cleanly.
Process & reporting traps
- Salesforce automation fires during the load. If the org has Flows or email alerts on Task, EmailMessage, or Opportunity creation, a migration can email real customers or flood reps with notifications. Turn them off, or bypass them for the migration user, before loading.
- Marketing attribution doesn't come along for free. Campaign history, touchpoints, and multi-touch attribution live in HubSpot's marketing data model and don't automatically become Salesforce Campaigns β that has to be rebuilt deliberately.
- Picklist and validation rule mismatches block inserts outright. If a HubSpot dropdown value doesn't exist as a Salesforce picklist value, or a validation rule expects a field HubSpot never required, records fail on load β usually in a batch, so the gaps aren't obvious until someone goes looking for a record that isn't there.
- Rebuilt automation can introduce new bugs, not just gaps. Translating a HubSpot workflow into a Salesforce Flow or Apex trigger is also where order-of-operations issues get introduced that never existed in the original workflow.
Before you migrate: clean the data first
Migrating dirty data into Salesforce just gives you the same mess with a more expensive interface. Before mapping starts:
- Run a duplicate check in HubSpot and merge what you can before export. Marketing forms create a lot of duplicate contacts, and de-duplicating after migration is significantly more work.
- Standardize picklist-style fields (industry, lead source, deal stage naming) so they map cleanly instead of creating dozens of one-off Salesforce picklist values.
- Identify and flag stale records β contacts with no activity in 2+ years are often better archived than migrated.
- Confirm which company each contact should be primarily associated with, where HubSpot's looser model allowed ambiguity.
Rebuilding HubSpot workflows as Salesforce Flow
This is the step teams most often underestimate. HubSpot workflows and Salesforce Flow aren't interchangeable β there's no export/import between them. Each workflow needs to be:
- Audited first. List every active workflow and what it actually does β plenty have quietly stopped mattering.
- Categorized by rebuild type. Simple field-update workflows usually become Record-Triggered Flows. Multi-step approval logic may need a Flow plus Approval Process. Anything touching an external system likely needs Apex or a middleware call.
- Tested against the same trigger conditions before going live, since a Flow that fires at the wrong time can do more damage than a workflow that simply doesn't fire.
Before the first record loads: a pre-flight checklist
These are small setup steps that are painful to fix after the fact. We check every one before a production run:
- Record counts pulled per object β contacts, companies, deals, tickets, and each activity type. This drives batch sizing, runtime, and whether the load needs multiple passes.
- Every custom property has a Salesforce field. Pull the full property list from HubSpot for each object and create the fields first.
- Owner-to-User table complete, including a default owner for records belonging to people who have left.
- "Set Audit Fields upon Record Creation" enabled for the migration user, if original created dates need to survive. It can't be applied retroactively.
- Automation paused or bypassed for the migration user β Flows, email alerts, assignment rules.
- Token scopes checked against every object type in scope.
- File and attachment handling tested on a real HubSpot file, including how long download links stay valid.
- Rollback plan agreed. External ID fields make migrated records easy to find and remove, but the plan should be written down, not improvised.
Testing, parallel run, and cutover
For anything beyond a trivial dataset, don't flip the switch in one step:
- Load a small test batch into a Salesforce sandbox first. Ten to twenty records per object, deliberately including edge cases β a deal from every pipeline, a contact at every lifecycle stage, a contact linked to several companies. Validation rules and permission gaps show up here, not in production.
- Run the full load in the sandbox, then have the business check it against HubSpot.
- Run a parallel period. One to two weeks with both systems live lets sales and marketing confirm Salesforce data matches what they expect, while HubSpot is still there as a safety net.
- Freeze HubSpot writes shortly before final cutover so nothing gets created there that doesn't make it into Salesforce.
- Migrate the delta, not the whole dataset again β only what changed since the snapshot.
- Reconcile counts per object. HubSpot count against Salesforce count for every object and activity type, so nothing is silently dropped.
- Keep HubSpot read-only (not deleted) for a defined period after cutover, in case something surfaces that wasn't caught in testing.
Common mistakes we see
Legacy fields and dead workflows get carried into Salesforce out of habit, not need β and now they're Salesforce's clutter problem too.
Stages from different pipelines get forced into one picklist, or mapped by internal ID, and a year of pipeline history becomes unusable.
Migrated tasks and emails trigger live alerts, and customers or reps get notifications about activity from two years ago.
Automation gets left for "later," and later becomes six months of manual work someone forgot a workflow used to handle.
Cutting over in one step means mapping errors get discovered by the sales team in production, not by testing beforehand.
Timeline and cost
As a rough guide: a straightforward migration (contacts, companies, deals, basic custom fields, one pipeline, under ~50,000 records) typically runs 3 to 6 weeks. Add activity history, multiple pipelines, workflow rebuilding, or several connected tools, and 8 to 12 weeks is more realistic. Cost tracks the same variables β raw record count matters less than the number of pipelines, custom objects, workflows, and integrations that need to be rebuilt rather than just copied.
Frequently asked questions
How long does a HubSpot to Salesforce migration take?
For a straightforward migration β contacts, companies, deals, and basic custom properties, under roughly 50,000 records β plan on 3 to 6 weeks. Add workflow rebuilding, activity history, multiple deal pipelines, or several integrated tools and it typically runs 8 to 12 weeks.
Do HubSpot contacts become Salesforce Leads or Contacts?
It depends on lifecycle stage, and it's a decision to make before migrating. A common rule is that early-stage records (lead, marketing qualified, sales qualified, opportunity) go to Lead, and customers go to Contact. Whatever rule you pick, apply it consistently and keep the original lifecycle stage on the record.
Will our HubSpot activity history come over to Salesforce?
It can. Notes, emails, calls, meetings, and tasks map to Salesforce Notes, EmailMessage, Tasks, and Events. Each type is pulled separately and linked through HubSpot's associations, so it adds runtime. Many teams migrate the last one to two years and archive the rest.
Can we keep the original created dates from HubSpot?
Yes, if the "Set Audit Fields upon Record Creation" permission is enabled for the migration user before loading. Without it, every record shows the migration date as its created date, which breaks age-based reports.
Can I migrate HubSpot data myself using CSV exports?
For a small, simple dataset, yes. It falls apart once you need to preserve relationships between records, migrate activity history, handle multiple pipelines, or keep HubSpot and Salesforce in sync during a transition β a proper ETL tool or scripted migration is worth it at that point.
Do HubSpot workflows transfer to Salesforce automatically?
No. Workflows and Flow are built on different logic models. Each one has to be reviewed and rebuilt β which is also a good opportunity to retire automations nobody actually uses anymore.
Should we run HubSpot and Salesforce in parallel during the migration?
For anything beyond a small, low-stakes dataset, yes. A one- to two-week parallel run lets the team validate Salesforce data against HubSpot before access is switched off, and catches mapping errors while they're still easy to fix.
Migrating from HubSpot to Salesforce?
Tell us what's in your HubSpot org today β how many pipelines, how much history, which tools connect to it. We'll scope the mapping, the workflow rebuild, and a realistic timeline.
Book a Free Consultation β