Two acquired companies, two HubSpot portals, one mandate to consolidate. The board wants it done in a quarter. Your migration partner says four weeks. Your gut says four months. Your gut is right, and even four months is tight if the portals have any real history.
There is no native “merge portal” button. There is a manual data move, and every shortcut taken in the merge is a discrepancy you will discover six months later when a report does not match. Here is the checklist that limits the damage.
What does not merge
Before anything else, list the things that simply cannot move.
- Original source attribution on contacts: lost or rewritten to “Offline” / “Imports”
- First conversion date and asset: lost or set to migration date
- Workflow enrollment history: gone
- Email send history per contact: gone
- Meeting and call history: gone unless re-imported as engagements, expensively
- Property history (the change log per property): gone
- Custom report definitions: must be rebuilt manually
If any of those matter to your business case, the merge is more expensive than the consolidation savings. Get clear before committing.
The asset inventory
Build it before any data moves. Spreadsheet, one row per asset class, two columns for source portals, one for target.
asset_class portal_a portal_b target_action
contacts 42k 18k merge with dedup on email
companies 9k 4k merge with dedup on domain
deals 3.2k 1.1k import all, owner remap
custom objects subs (4k) none rebuild schema, import
workflows 87 34 audit, keep 30, rebuild
forms 210 92 map embeds, keep 50
landing pages 120 44 rebuild on target theme
emails 600 250 archive both, do not import
lists 180 60 rebuild active only
properties (contact) 340 180 consolidate to 250
properties (deal) 110 80 consolidate to 120
sequences 42 18 rebuild top 15 by usage
templates 320 120 curate to 80
The “target_action” column is the contract. Sign off on it before you start.
Dedup, but smarter than email
Contact dedup on email at portal merge time is the same problem as import dedup, with an extra wrinkle: same person, different email, two real records. A person who worked at Company A and now works at Company B has two valid contact records and they are not duplicates.
Heuristic for merge dedup:
- Same email: definitely merge
- Different email, same name, same current company: probably merge after human review
- Different email, same name, different company: probably keep separate
- Different email, no name overlap, same phone: review
Do not auto-merge anything past the first row. Build a review queue.
Owner remapping is its own project
Portal A’s owners do not exist in Portal B. You can invite them, but their user IDs are different. Every record’s hubspot_owner_id needs translation.
const ownerMap = {
"1234567": "9876543", // source user id : target user id
"1234568": "9876544",
"1234569": "UNASSIGNED", // source user no longer at company
};
async function remapOwners(record) {
const sourceOwner = record.properties.hubspot_owner_id;
const targetOwner = ownerMap[sourceOwner] || "UNASSIGNED";
return { ...record, properties: { ...record.properties, hubspot_owner_id: targetOwner } };
}
UNASSIGNED records need a fallback owner, typically a sales ops “house” user. Document the fallback before cutover. Do not leave records ownerless.
Lifecycle stage reconciliation
Two portals, two definitions of “MQL.” Portal A’s MQL might be Portal B’s SQL. The pre-merge work is to map every stage value in source to the target stage taxonomy.
portal_a_stage target_stage
subscriber subscriber
lead lead
mql mql
sales-qualified sql
opportunity opportunity
customer customer
evangelist customer
portal_b_stage target_stage
contact lead
warm_lead mql
hot_lead sql
trial opportunity
paying customer
churned other (custom)
Note “churned” does not exist in standard HubSpot lifecycle. Create a custom “Other” lifecycle stage or use a separate “Customer Status” property. Do not lose the churn signal.
API rate budget for the cutover
A real merge moves hundreds of thousands of records. HubSpot’s API rate limits are per-portal, per-app. Burst window: 100 requests per 10 seconds for most endpoints. Daily limit: usually 250,000 to 1 million depending on tier.
Math: 50,000 contacts at batch size 100, plus deals plus companies plus engagements, easily 30,000 to 60,000 API calls. Burst rate of 10/sec sustained means 5,000 to 10,000 seconds of pure API time, plus retries.
Plan the migration over a maintenance window. Document the rate-limit fallback (backoff, retry, log) before the cutover, not during.
See HubSpot API rate limit survival for the operational playbook.
Tracking code transition
The HubSpot tracking script on your websites points to one portal. After cutover, it must point to the target portal. The transition window is the problem.
- Day -1: tracking still on source portal, all visitor events captured there
- Day 0: cutover, tracking script swapped
- Day 0 onward: visitor events go to target portal
There is no native “merge tracking history.” Visitor sessions in source portal stay in source portal. The first 30 days post-cutover, your attribution reports in target portal will look thin because the history is in the archived source portal.
Communicate this upfront. Marketing leadership panics when they see the dip and find out after.
Email send reputation
If both portals send from the same domain, the migration is easier. If they send from different domains and you are consolidating to one, you have a sender reputation problem. The new sender starts from zero with mailbox providers.
Warm the new sender before cutover. Three to four weeks of gradually increasing volume from the new domain. Do not cutover and then send a 50k blast on day one.
See HubSpot email deliverability monitoring for the warm-up cadence.
The cutover day runbook
The day-of checklist, in order.
- Final delta export from source portal (anything since last full export)
- Pause all active workflows in source portal
- Final import to target portal
- Spot-check 50 records in target across contacts, companies, deals
- Swap tracking code on websites
- Update DNS for email sending domain (if applicable)
- Activate target portal workflows in stages, not all at once
- Send internal email to sales: target portal is live, source is read-only
- Lock source portal users to read-only access
- Monitor for 72 hours before declaring done
The 72-hour monitor catches the workflow that re-enrolls 50k contacts because somebody re-activated everything at once. See the workflow throttle pattern.
Bottom line
- Inventory before you move; the spreadsheet of asset classes and target actions is the contract.
- History does not merge: original source, first conversion, engagement history, workflow enrollment all reset.
- Dedup on email then by human review for ambiguous matches; do not auto-merge beyond exact email.
- Owner remap, lifecycle stage reconciliation, and tracking transition are three separate projects inside the merge.
- The cutover runbook is sequential and 72-hour-watched; treat it like a deploy, not a copy-paste.