Migration or integration? Why most HubSpot implementations are migrations first

A migration moves history once and ends. An integration keeps two systems agreeing forever. One question tells you which you are buying, and when.

  • Comparison

The two words get used as though they were the same purchase. They are not, and the difference decides the shape of the work, the skills it needs and what finished looks like. Most teams asking us for an integration need a migration first, and most teams asking for a migration discover partway through that something has to keep running afterwards.

Here is the distinction we work to, the question that settles it, and the load pattern we use every time.

The difference in one line each

A migration moves history once. It has an end. Success is a reconciliation: the counts on both sides agree, the associations are wired, and the old system can be switched off.

An integration keeps two systems agreeing over time. It has no end. Success is what happens on a bad night: a watermark that held instead of advancing past a window it never read, a replay that resumed from a checkpoint, an association that queued for repair rather than vanishing into a log line.

They fail differently too. A migration that goes wrong is loud and immediate, and you find out during reconciliation. An integration that goes wrong is quiet. It drifts, and somebody notices weeks later that a report is short.

The one question that settles it

Ask whether anyone will change a record in the source system after go live.

If the answer is no, because the source is being retired or frozen, it is a migration. Build a loader, run it, reconcile it, delete it. There is no service to host, no scheduler and nothing to monitor, and any money spent on those is wasted.

If the answer is yes, because both systems stay in service and both keep changing records, it is an integration, and the approach guide covers which kind.

Most projects answer yes, and that is exactly why the migration comes first. An integration has nothing to attach to until the records exist in HubSpot and carry the keys the sync will match on. Every integration we have built opened with a backfill, which is a migration wearing a different name. A managed sync for a manufacturer opened by loading 20,004 orders reaching back to July 2020. Doing that backfill on the same keys the ongoing sync uses is the difference between one dataset and two.

How we build a one time load

The pattern is the same whatever the source, and none of it happens in a spreadsheet.

Properties first. Create the custom HubSpot properties that hold each object’s source identifier and let HubSpot enforce them as unique. Stamp a source system and a migration batch onto every record, which makes the cohort queryable later and makes a rollback a query rather than an archaeology exercise.

Then clean in code, not by hand. Phone formats, date sentinels, country spellings and split email cells are transform rules that have to run the same way on the sandbox load and the production load, six weeks apart.

Then upsert object by object in parent first order, keyed on the source identifier, on HubSpot API version 2026-03. Companies before contacts, contacts before the activity that hangs off them. Never key on an email address.

Then build associations in a pass of their own, from identifiers resolved out of the load responses rather than guessed. Then reconcile, before the next phase starts. Then run the whole thing twice in a sandbox, because a second run that updates in place and creates no duplicates is the only proof that the load is idempotent.

Cutover is a token swap. The same code, pointed at production, behind a portal allowlist that fails the run closed when the connected portal is not the one the environment expects. That sequence is the same one we run on every integration, set out in how we scope an integration.

Why not the import tool

Two routes look cheaper and are not, and we rule both out on real migrations.

The no code importer in HubSpot caps a job at about two objects. A four object load with six association types becomes a chain of manual, click driven imports that nobody can repeat for a sandbox and then again for production. Repeatable is the whole point.

The CSV Imports API can do the whole thing in one job, and it carries two costs that only show up afterwards. Records loaded that way are not filterable in lists, reports and workflows. Activities cannot be updated once created, which removes the fix path on a load nobody gets perfect the first time.

Both are fine for a one off list of contacts. Neither survives contact with a multi object migration that has to run twice.

What that looked like on a distributor’s load

A dual brand packaging distributor was retiring the sales layer sitting on top of its Pronto XI ERP, against a contract date rather than a preference. That layer holds no database of its own, so four exported files were the only snapshot that would ever exist.

The profile was 28,818 company records, 11,875 contacts after dedupe, 2,641 opportunities and 31,556 call records. The data closed the obvious route immediately: 5,036 contacts have no email address at all, and 416 email addresses are shared by 912 contacts. Email could not be the contact key. One fact made the rest workable, which is that referential integrity across the four files was clean, with no orphans on any foreign key, so a keyed load in parent first order would resolve every link.

A validation run settled five things the documentation had not. A company can carry a unique property, so no search then upsert workaround was needed. Calls cannot be upserted by any key, so the loader creates them and deduplicates against a map of source note ids read back from the portal. Call duration is stored in milliseconds, confirmed by writing a 60 minute call and reading back 3,600,000. The stage mapping lands on the right internal identifiers. And a second full run updates in place and creates no duplicates.

The full sandbox run reconciled exactly: 7,942 companies, 10,861 contacts, 3,598 deals and 26,006 calls, with the transform output matching the portal count on every object and zero failed records. Every contact, deal and call carried its company association, and 11,256 calls also reached a contact.

The load is smaller than the export on purpose. Account scope moved to an ERP first set days before the full run, web shop accounts were left out by agreement, and accounts with no purchase activity since 2020 were filtered out. That scope change is the honest lesson in the whole engagement: settle which accounts and which objects are in scope before mapping the entire export, not after. The full account is here, and the system page is sales-i and Pronto XI.

A four object keyed migration with an associations pass sits in the 70 to 150 hour build band, with 10 to 20 hours of discovery ahead of it. A single object load with no associations is closer to 40 to 60.

When a sync has to keep running

Keep one running when the source system survives go live and stays authoritative for something. A publisher’s backend stays authoritative for books, subscriptions and pricing while the sales desk works in HubSpot, so seven flows run on a schedule rather than one loader running once.

Do not keep one running out of nervousness. A sync against a system nobody edits any more is a service to patch, monitor and pay for, and it will eventually overwrite something a person fixed in HubSpot by hand. Retire the loader when the source is retired.

The work that carries from the migration into the sync is the valuable part: the object model, the match keys and the field mapping. Key every object on the source system’s own identifier at migration time and the integration becomes a scheduler over the same upsert logic, rather than a rebuild.

Frequently asked questions

Is a HubSpot data migration the same as an integration?

No. A migration moves history once and then stops, and it is finished when the counts reconcile and the old system is retired. An integration keeps two systems agreeing indefinitely, and it needs watermarks, checkpoints, replay and monitoring that a migration has no use for. Most projects need both, with the migration running first.

Can we use the HubSpot import tool for a data migration?

For a one off contact list, yes. For a real multi object migration, no. The no code importer caps a job at roughly two objects, and records loaded through the CSV Imports API are not filterable in lists, reports and workflows afterwards. Activities loaded that way cannot be updated, which removes the fix path on a load that needs a second pass.

What should the match key be in a HubSpot migration?

The source system’s own identifier for that object, held in a custom HubSpot property that HubSpot enforces as unique. Never the email address and never a company name. In one distributor’s export, 5,036 contacts had no email address and 416 addresses were shared by 912 contacts, so an email keyed load would have merged real people together.

What happens to call and email history in a migration?

Activities are the awkward object. They cannot carry a unique property, so they cannot be upserted, which means the loader has to create them and deduplicate against source identifiers read back from the portal. Each one also needs an explicit historical timestamp, or the whole log collapses onto the day of the load and the timeline is worthless.

How do we know the migration actually worked?

Reconciliation, run per object before the next phase starts. Compare the transform output against the portal count for every object, count the associations, and count the failures. The target is zero failed records and matching counts on both sides, verified in a sandbox first. A run that reports success is not the same as a run that moved the rows.

Partnerships and accreditations

  • HubSpot Elite Solutions Partner
  • HubSpot Custom Integration Accreditation
  • HubSpot CRM Data Migration Accreditation
  • HubSpot Solutions Architecture Design Accreditation

HubSpot accreditations

  • HubSpot Onboarding Accreditation
  • HubSpot Academy Accreditations

Industry specializations

  • HubSpot Healthcare Industry Specialist
  • HubSpot Financial Services Industry Specialist
  • HubSpot Manufacturing Industry Specialist
  • HubSpot Quote to Cash Capability

Partnerships

  • Google Partner
  • Anthropic Solutions Partner

Want a second opinion on the approach?

Send the systems, the direction and the record volume. We reply with a scope and an effort band within one business day.

Press Enter or comma to add another system.

Add more detail (optional)

Optional. A sentence or two about the workflow is enough.

We use these answers to scope the work and for nothing else. Privacy policy.

Prefer to talk it through first? Book a technical scoping call

Talk to the people who build these

  • Nijat
  • Sean
  • Prash

Book a scoping call