Skip to main content
Most teams need no code. Settings → Migrate CRM pulls contacts, companies, deals, pipelines, custom properties, engagements and opt-outs straight from HubSpot in the background, resumably, with HubSpot ids kept. Step-by-step guide. This page is for running the same move from code, when you want to transform data on the way or bring attachments across.
HubSpot keeps a record’s history as engagements: notes, emails, calls, meetings and tasks, each with a timestamp, an owner and the records it is associated with, plus attachments in its Files tool. Orbit keeps the same things: notes and tasks, logged activities for emails, calls, meetings and messages, and files on records. This page maps one to the other.

The script

scripts/maintenance/migrate-from-hubspot.ts in the Orbit repository walks a portal’s companies, contacts, deals, engagements and attachments into Orbit through the CRM API, in batches, with a HubSpot service key (or an existing private app token) and an Orbit API key (the CRM agent preset plus activities:write and files:write). It is safe to re-run: every row carries its HubSpot id as external_id under the source hubspot, so a second pass updates instead of duplicating.
It follows the order in Migrating from another CRM: members, structure, companies, contacts, deals, history, files, do-not-contact. --dry-run reads HubSpot and reports what would be written, --only runs a subset of the phases and --limit caps each phase for a trial run.

Field mapping

What the script sends, and what any other code reading HubSpot’s API should send. Timestamps from HubSpot are Unix milliseconds; Orbit accepts them as they are or as ISO 8601.

Every engagement

Notes

Emails

Calls

Meetings

Tasks

Communications (SMS, WhatsApp, LinkedIn)

Check the result

Open a few contacts in Orbit: the timeline shows the migrated emails, calls, meetings and notes at the dates they happened, each marked with who did it and that it came from HubSpot. GET /api/v1/timeline reads the same feed. If a run went wrong, POST /api/v1/activity-imports/{id}/undo removes everything that activity import created; contacts have their own undo on the contact import.