HubSpot
Stream HubSpot CRM objects into Streamkap using a HubSpot Private App access token
The HubSpot source pulls CRM objects — contacts, deals, companies, and more — out of your HubSpot portal and streams them into Kafka topics, one topic per object. Records land flat in your destination like any other Streamkap source, so every existing destination consumes them unchanged.
How It Works
- Authentication — Streamkap calls the HubSpot API with a Private App access token you provide (see Prerequisites).
- Incremental sync — On the first run each selected object is read in full. After that, the connector polls on a schedule and fetches only records that changed since the last poll, tracking a per-object cursor.
- Deletes — A periodic sweep detects records archived in HubSpot and emits a tombstone (
__deleted = true) so downstream sinks flip the row to deleted; a slower daily reconciliation pass also catches deletes that leave no archived record, such as merges and permanent purges (see Deletes and tombstones). - Rate limiting — The connector paces its requests to stay under your portal’s HubSpot API limits (see Behavior & Limits).
Each object you select produces a topic named source_<id>.hubspot.<object> — for example source_<id>.hubspot.contacts.
Prerequisites
- A HubSpot account with permission to create a Private App.
- A Private App access token with the CRM read scopes for the objects you want to sync.
- A Streamkap workspace with permission to create source connectors.
Required scopes
Streamkap reads each resource you select through HubSpot’s CRM API, so the Private App needs the read scope HubSpot requires for that object. Streamkap never writes to your portal, and it needs no scope for anything you have not selected.
Scope names, and the account tier each is available on, are HubSpot’s own and change independently of Streamkap — take them from HubSpot’s scope reference. Three mappings are worth knowing when you look them up:
pipelinessyncs deal pipeline definitions, so it needs deal read access — not a scope of its own.ownersis read from HubSpot’s owners API, not from the CRM objects API.- Streamkap also reads each selected object’s property list, which is how it picks up your custom and newly-added properties. On the portals we have tested, the object’s read scope already covers this. If it does not on yours, Test connection says so and names the object.
Optional: crm.schemas.custom.read
Grant this only if you want to sync custom objects. It is what lets Streamkap list your portal’s custom object definitions so it can offer them in the Resources picker.
Without it, the Settings tab shows a note that it could not read your custom-object definitions, and everything else keeps working normally — the standard resources you selected are unaffected. If your portal has no custom objects (they need a higher HubSpot tier), there is nothing to grant and nothing to fix; you can ignore the note.
HubSpot Setup
1. Create a Private App
- In HubSpot, go to Settings → Integrations → Private Apps.
- Click Create a private app, give it a name (for example
Streamkap), and open the Scopes tab. - Select the read scopes for the objects you want to sync (see Required scopes).
- Click Create app and confirm.
2. Copy the access token
- Open the app and go to the Auth tab.
- Under Access token, click Show token, then Copy.
- Keep this token handy for the Streamkap setup below — treat it like a password.
Streamkap Setup
Follow these steps to configure your new connector:
1. Create the Source
- Navigate to Add Connectors.
- Choose HubSpot.
2. Connection Settings (Auth tab)
- Name: A name for your connector (for example
hubspot-prod). - Access Token: Paste the HubSpot Private App access token from HubSpot Setup. It is stored encrypted at rest and never shown again.
Optionally click Test connection. This runs a read-only check against HubSpot — nothing is created or written — and reports one of:
- Connection verified, with the HubSpot account the token belongs to. Worth a glance: it confirms you pasted the token for the portal you meant.
- Connection failed — the token is invalid, revoked, or its Private App was deleted.
- Some resources need attention — the token works, but one or more of the objects you selected cannot be read: usually a missing read scope, or a custom object whose qualified name is not in this portal. The result names each object it could not read, so you know exactly what to fix in the Private App.
Testing is optional. Streamkap validates the token again when you save, whether or not you tested.
Click Next.
3. Settings tab
- Resources: Select the HubSpot objects to sync. The default is contacts and deals. See Supported Resources for the full list.
- Backfill start date (optional): The earliest record modification time to sync, as an ISO-8601 date or datetime —
2026-01-01, or2026-01-01T00:00:00Z. A value with no timezone is read as UTC. Leave it empty to sync all history. See Backfill start date. - Sync all properties: On by default, and best left on. It syncs every property an object exposes, including your custom properties and any you add in HubSpot later. Turn it off to pick individual properties instead — see Properties and custom fields.
The panel beside the form lists the Kafka topics your selection will produce — one topic per resource.
4. Review and create
The last step of the wizard is Review. It lists everything you entered, with the access token masked. Click Create.
Streamkap validates the token against HubSpot before the source is created — an invalid or under-scoped token is rejected here with an actionable message, before anything is saved.
Editing the Source
An existing HubSpot source has Status, Auth, Settings, Destinations and Config History tabs, plus a Logs link. There is no Schema tab: an API source has no table schema to include or exclude, and it does not expose a schema snapshot.
Rotating the access token
The token is write-only. It is never displayed again, so the Auth tab shows it as stored rather than as a value. Leave it untouched and Streamkap keeps the credential it already holds; click Replace and type a new one to rotate it.
Saving a replacement token takes effect immediately: it clears an authentication failure and any per-object scope suspensions in one step, without waiting for the automatic retry.
Test connection works here too, and tests the stored token — you do not need to re-enter it just to check that it still works. This is the quickest way to confirm a Broken source is a credential problem rather than a scope one.
Custom objects
Once a source exists, the Settings tab discovers your portal’s custom objects and adds them to the Resources picker alongside the eight standard ones. They are listed by their fully-qualified name (p<portal-id>_<name>, or 2-<object-type-id>).
Custom objects are not offered on the create form: discovery needs the saved token to ask HubSpot what your portal has. So create the source with the standard resources you want, then add custom objects on the Settings tab.
Custom objects are a higher-tier HubSpot feature, and syncing them needs two scopes the standard resources do not: crm.schemas.custom.read to list the definitions and crm.objects.custom.read to read the records. Both are optional — a portal with no custom objects needs neither.
Sending Topics to a Destination
HubSpot topics reach a destination without you building a pipeline by hand. Streamkap creates and maintains the pipeline for you — the Pipelines list labels it Auto-managed.
You can start from whichever end you are already looking at:
- From the source — open its Destinations tab, click Send topics, pick a destination.
- From the Topics page — select the topics you want and choose Send to destination.
- From the destination — open it and click Add topics.
To stop delivering a topic, remove it from the source’s Destinations tab — Detach all clears a destination in one go. Removing the last topic removes the destination from the source entirely.
Supported Resources
Select any of the eight standard CRM objects:
| Resource | HubSpot object |
|---|---|
contacts |
Contacts |
deals |
Deals |
companies |
Companies |
tickets |
Tickets |
line_items |
Line items |
products |
Products |
owners |
Owners |
pipelines |
Deal pipeline definitions. Ticket pipelines are not synced. |
Not yet supported
The following are deferred and cannot be selected yet. Requesting one is rejected at validation with a message saying the resource is “deferred pending pilot demand — contact support to enable them”:
- Associations (object-to-object relationships)
- Quotes
- Engagements (notes, calls, emails, meetings)
- Property history
Need one of these? Contact Streamkap support — pilot demand drives the roadmap.
Behavior & Limits
Incremental sync
After the initial full read of each object, every poll fetches only records that changed since the last cursor position. A poll where nothing changed in HubSpot produces zero new records — this is expected, not a fault.
Backfill start date
By default the first sync of an object reads all of its history. When you don’t want years of records you will never query, set a Backfill start date on the Settings tab: the first sync starts from that instant instead, and records last modified before it are never fetched.
The value bounds the first sync of each object and nothing else. Once an object has synced, the connector resumes from its own cursor and the backfill start date no longer applies to it — raising it later hides nothing, and lowering it reaches no further back. An object you add to Resources afterwards is new, so its first sync uses whatever the value is at that point.
Properties and custom fields
Sync all properties is on by default, and it is what keeps a HubSpot source in step with your portal. With it on, Streamkap reads each object’s live property list on every poll and syncs every property that object exposes — the standard ones, your custom properties, and any property added since the last sync. Add a property to Contacts this afternoon and it starts flowing on the next sync, with nothing to reconfigure in Streamkap.
Turn it off and a Properties picker appears, listing every property your selected objects expose. The source then syncs exactly what you choose there and nothing else. The reason to do that is a destination table whose consumers depend on its current columns and must not widen — or simply a portal with hundreds of properties where you only want a handful.
Two things are always synced whatever you pick: each object’s last-modified property, which is how the source knows what changed, and hs_object_id, which identifies the record. A selection that leaves them out would break incremental syncing, so they are added back for you.
Property names in the picker are HubSpot’s internal names, not the labels shown in the HubSpot UI — the picker shows both so you can tell them apart. A name only has to exist on one of your selected objects; syncing dealname alongside Contacts and Deals gives you the column on Deals and no column on Contacts, which is what HubSpot itself does. If you type a name that exists on none of them, Streamkap rejects it when you save rather than letting you believe it synced, because HubSpot silently ignores a property it does not recognise.
The picker only appears on a source you have already created: reading your portal’s property list needs your credentials, and syncing everything is the default, so a new source never has to answer the question.
New properties are added to your destination table automatically when the destination evolves its schema, which is the default. If a destination is set not to evolve, its existing columns stay as they are and the new property arrives on the topic without being added to the table. See Schema Evolution.
What the columns are called
HubSpot returns a record’s properties inside a properties object, and Streamkap keeps that shape on the topic so destinations that store nested data natively receive it unchanged. Destinations that need flat columns — every SQL database and warehouse — get the object flattened, which prefixes each column with properties_:
| HubSpot property | destination column |
|---|---|
dealname |
properties_dealname |
dealtype |
properties_dealtype |
hs_priority |
properties_hs_priority |
The record’s own top-level fields — id, createdAt, updatedAt, archived — carry no prefix, because they are not properties. If a property looks missing from your table, check for the prefixed name before anything else.
Daily quota pacing
The connector paces its API calls to stay under your portal’s HubSpot limits, self-capping below the per-second account limit so it leaves headroom for your own integrations sharing the same portal.
If the portal’s daily API budget is still exhausted, the connector pauses ingestion and reports an actionable status message showing when it will resume. Polling resumes automatically at the start of the next UTC day, and no data is lost — ingestion continues from where it paused. If you hit this regularly, reduce the number of selected resources or the load from other integrations on the same portal.
Deletes and tombstones
Streamkap detects deletes two ways.
The archived sweep. When a record is archived in HubSpot, a periodic sweep (roughly every 6 hours) detects it and emits a record with __deleted = true, so your destination’s tombstone/delete handling flips the row to deleted. Expect archived records to surface as deletes within one sweep interval. The sweep covers the six CRM object types — contacts, deals, companies, tickets, line_items, products — and any custom objects you sync.
Reconciliation. Some deletes leave no archived record for the sweep to find: a record merge retires the losing record, and a permanent purge (including a GDPR erasure) removes it outright. A slower reconciliation pass — about once a day — closes that gap. It re-lists the live records for each of those same objects and tombstones any that have disappeared, so merged and purged records surface as deletes too, within about a day.
Per-object permissions
Scope problems are handled per object, not per connector. If the token is valid but missing the read scope for one selected object, only that object’s syncing pauses — every other object keeps streaming. The connector shows Broken, and its status message names each denied object and the fix.
A denied object recovers on its own: Streamkap retries it periodically (roughly every 15 minutes), and syncing resumes as soon as the scope is granted in HubSpot. You can also clear it immediately by re-saving the connector config — or remove the object from Resources if you don’t need it.
A fully invalid or revoked token is different: all syncing stops, and the connector stays Broken until you save a replacement token — it does not retry a rejected credential on its own.
Connector status
| Status | Meaning |
|---|---|
| Pending | The source is saved and Streamkap is still deploying it. It moves to Active on its own once the connector starts; there is nothing to do. |
| Active | Polling normally. A trace message may note a temporary daily-quota pause (see above). |
| Paused / Stopped | You paused or stopped the source. Resume to continue from the last cursor. |
| Broken | The connector needs attention. The status message says which case you are in: “rejected the API token” means the token is revoked or invalid — all syncing stops until you replace it; “denied access to …” names objects missing a scope — those objects pause while the rest keep streaming, and they recover automatically once the scope is granted. See Troubleshooting. |
Troubleshooting
Broken: "rejected the API token" (401 / authentication error)
The HubSpot access token was rejected — it is invalid, was revoked, or the Private App was deleted. All syncing stops, and Streamkap does not retry a rejected credential on its own: the source recovers when you save a replacement token.
Resolution:
- Open the source’s Auth tab in Streamkap and click Test connection. It tests the stored token, so you can confirm the credential is the problem before touching HubSpot.
- Confirm the Private App still exists in HubSpot (Settings → Integrations → Private Apps) and its token has not been rotated.
- Re-copy the access token (the service token / legacy Private App token — the same value) from the app’s Auth tab.
- Back on the source’s Auth tab, click Replace, paste the fresh token, and save. The source recovers immediately.
Broken: "denied access to …" (403 on specific objects)
The token is valid but is missing the read scope for the named objects. Only those objects pause — every other object keeps streaming — and the status message lists each denied object.
Resolution:
- Add the missing read scope for each named object in HubSpot (Private App → Scopes). See Required scopes.
- That’s usually all: Streamkap retries denied objects roughly every 15 minutes, so syncing resumes on its own once the scope is granted. If HubSpot rotated the token when you changed scopes, replace it on the source’s Auth tab.
- Don’t need the object? Remove it from Resources on the Settings tab instead.
- Test connection on the Auth tab shows the same information on demand — it names each object the token cannot read.
No new records on the second poll
This is expected. The source is incremental — after the first full read it only emits records that changed in HubSpot since the last poll. If nothing changed, a poll produces zero records. Edit a record in HubSpot and it appears on the next poll.
A property I added in HubSpot isn't in my destination
New properties are picked up automatically on the next sync, so the usual causes are one of these:
- You’re looking for the unprefixed name. In a SQL destination the column is
properties_<name>, not<name>— see What the columns are called. - The sync hasn’t run yet. The property list is re-read each poll — wait for the next one, and for a record carrying the new property to change.
- Sync all properties is off. With it off only the properties in your Properties selection are synced, so a newly added one is not included until you add it there. Add it, or turn Sync all properties back on in the Settings tab. See Properties and custom fields.
- The destination isn’t evolving its schema. The property is on the topic but the destination is not adding the column. See Schema Evolution.
A new property is only populated for records that change after you add it — HubSpot does not report untouched records as changed. See Incremental sync.
Older records are missing after the first sync
Check the Backfill start date on the Settings tab. When it is set, the first sync of each object skips records last modified before that instant.
Clearing or lowering the value does not bring them back — the bound applies to an object’s first sync only, and that object is now on its own cursor. Remove the object from Resources and add it back to read it again from the new bound. See Backfill start date.
Ingestion paused — daily quota exhausted
The portal’s daily HubSpot API budget was used up. The connector pauses and resumes automatically at the start of the next UTC day — no data is lost.
If it recurs:
- Reduce the number of selected resources.
- Reduce the API load from other integrations sharing the same HubSpot portal.
A deleted record still shows in my destination
Deletes are detected by a periodic sweep (roughly every 6 hours), not on every poll, so an archived record can take up to one sweep interval to surface as a tombstone (__deleted = true). A merge or a permanent purge leaves no archived record, so it is caught instead by the slower daily reconciliation pass and can take up to about a day.
Cases where the delete never arrives:
- The record was an owner or a pipeline. Neither has an archived state in HubSpot, so neither the sweep nor reconciliation can tell a removed one from a live one. See Deletes and tombstones.
- The object is very large and the delete was a merge or purge. Reconciliation does not run on very large objects (roughly tens of thousands of records), so on those a merge or purge falls back to the archived sweep, which does not see it. Archived deletes are still caught.
Error: resource "deferred pending pilot demand"
Associations, quotes, engagements, and property history are deferred and cannot be selected yet. Remove the deferred resource from your selection, or contact support to have it enabled. See Not yet supported.
See Also
- Sources — managing source connectors, status, and config history.
- Salesforce CDC — streaming CRM change events from Salesforce.