CampaignStack with HubSpot: every touch lands on the contact timeline
HubSpot is where the client's sales team lives, and a LinkedIn conversation they cannot see is a conversation that did not happen. Connect a portal with a private app token and a CRM sync step upserts the lead as a contact, associates its company and logs the outreach message as a note on the timeline. In the other direction, the portal's contacts, or one contact list, import as leads you can score and work.
The record. Deals, pipeline stages, the sequence of calls and emails the account executive logged, the reporting the client's leadership reads. Nothing about a LinkedIn touch should live anywhere the rest of the team does not look, and HubSpot is where they look. It is also the system that decides a contact's lifecycle stage, which is why the sync never writes one unless you tell it to.
The LinkedIn side of the relationship. HubSpot's own Sales Hub LinkedIn features stop at viewing a profile from a record; it does not source people from a competitor's post, score them against an ICP, draft a message in the account owner's voice, send it inside a daily budget or notice the reply. CampaignStack does that, then writes what happened onto the contact, keyed by email, so the same person is never created twice.
Where each side attaches
What HubSpot does alone, and what changes once the two are connected.
| HubSpot alone | With CampaignStack | |
|---|---|---|
| Outreach on the timeline | Emails and calls logged by the sales team; LinkedIn touches typed in by hand, when they are. | A CRM sync step in the workflow upserts the contact by email and logs the message as a note with the send time, on every send or only on the exit you choose. |
| Companies | Company records keyed by domain, associated by hand or by HubSpot's own matching. | When the lead's company resolves to a real domain, the step upserts the company and sets the primary association. A personal email domain is never used. |
| Contacts into outreach | A list of contacts nobody has touched on LinkedIn. | Import the whole portal or one list as private leads, scored against the campaign's ICP, with exclusions checked at every send. |
| Agents | HubSpot's own MCP server exposes the CRM to an agent, and only the CRM. | One agent holding a CampaignStack key syncs a lead, imports a list or checks the connection through the MCP tools below, on top of what it already does with HubSpot's. |
Set it up
A private app token on the HubSpot side, one paste on ours, then a step in the workflow. No OAuth, one portal per workspace.
- 1
Create a private app in HubSpot
In HubSpot, open Settings, then Integrations, then Private Apps, and create one. On its Scopes tab, add the scopes below; the search box only finds exact names, so paste them one at a time. Copy the access token, which starts with pat-na1- or pat-eu1-. Both regions work the same way.
# Required crm.objects.contacts.read crm.objects.contacts.write # Optional crm.objects.companies.read # associate the lead's company crm.objects.companies.write crm.schemas.contacts.write # one-time "campaignstack_source" property crm.lists.read # import from a contact list - 2
Connect the portal in CampaignStack
Open the workspace, then Integrations, then Connect on the HubSpot card, and paste the token. The connect runs a read ping and a zero-side-effect write probe, so a missing write scope is named on the spot instead of failing on the first sync. The token is stored encrypted and never returned by any query or tool.
- 3
Add a CRM sync step to the workflow
In the workflow editor, add the HubSpot CRM sync step where you want the write to happen: right after the send, or on the replied or meeting booked exit. Company association and the note are toggles on the step; lifecycle stage is a field you leave empty unless the client wants one. A lead without an email is skipped and the workflow moves on.
email -> email (the dedup key, lowercased) firstName -> firstname lastName -> lastname title -> jobtitle company -> company linkedInUrl -> hs_linkedin_url -> campaignstack_source = "CampaignStack" lifecyclestage is never sent unless you set it on the step - 4
Import contacts, or drive it from an agent
On the Integrations page, the HubSpot card imports the whole portal or one contact list as private leads. The same operations are MCP tools, so Claude or any MCP client with a user key can connect, sync one lead or import a list.
campaignstack_connect_hubspot { workspaceId, accessToken } campaignstack_sync_lead_to_hubspot { workspaceId, leadId, logNote: true } campaignstack_list_external_lead_lists { workspaceId, provider: "hubspot" } campaignstack_import_external_leads { workspaceId, provider: "hubspot", listId }
Upsert, never create
HubSpot dedupes contacts on email, so the sync uses email as the upsert key: the same person touched by two workflows or two accounts is one contact with two notes, not two contacts. It follows that a lead without an email cannot be synced. Run a contact enrichment step before the sync, or let the Contact Finder fill the address, and the sync picks it up on the next send.
Imported contacts stay yours
A CRM export is the client's own data, so contacts imported from HubSpot land workspace-scoped: they never enter the shared lead database other workspaces read from and never cross a workspace boundary. Public LinkedIn data scraped later for the same people follows the normal rules. In the other direction, the sync writes only the fields listed above, and only fills a lifecycle stage you set explicitly, because a downgrade on an existing customer is the one write a CRM never forgives.
What beta means here
Contact import is verified against live portals. The sync step is unit-tested and dispatches normally, but a few paths have not yet been walked inside a real portal's UI: re-running the same sync twice, a revoked token, a rate limit mid-run. Spot-check the first synced contact in HubSpot. The marker comes off when those are confirmed, not before.
Frequently asked questions
Do I need OAuth or a HubSpot app listing?
No. A private app token is created in your own portal in a minute and pasted once. There is no marketplace listing and nothing to approve.
Which HubSpot tier is required?
Contacts, companies and notes are available on every tier, including free. If HubSpot reports a scope as missing even though it is ticked, the portal's tier is gating that object, not the scope set; the connect error names which one.
Does the sync write deals or pipeline stages?
No. Contacts, an optional company association and a note. Deals, custom objects and timeline events are out of scope on purpose; a private app cannot write timeline events anyway.
Is there a comparison of the two?
No, and there will not be one. HubSpot is a CRM and CampaignStack is not. This page assumes you run both.
Every claim about the vendor on this page was checked against these pages on its own site:
Running something else next to it?
Get started
Start building your stack.
Your LinkedIn accounts will be safer with CampaignStack than doing it by hand. That's not a pitch. It's a measurable claim.