- Documentation
- Integrations
- Apps
- HubSpot integration
HubSpot integration
Watch HubSpot CRM records change in real time and create, update, search, and merge them on behalf of your clients.
What it does
The HubSpot integration connects a client CRM once and gives you the whole CRM surface: 33 real-time webhook triggers, 7 polling triggers, and 38 actions covering records, associations, properties, lists, owners, pipelines, files, forms, workflows, subscription preferences, custom events, and blog posts.
The six generic Record actions — Create, Update, Get, Delete, Search, and List — take an objectType and work against every HubSpot object type, not a fixed four. The dropdown offers all 35 standard types (contacts through quotes, invoices, orders, carts, leads, and the newer appointments/courses/listings/services/projects objects), and switching the field to expression mode lets you target a custom object by its objectTypeId (2-XXXXXXX).
Every HubSpot trigger and action currently ships with a beta badge. Trigger nodes are hidden in the editor unless beta nodes are turned on for your workspace.
Set up your OAuth app (agency, one time)
A HubSpot connection runs through your agency's own OAuth app unless TaskJuice's HubSpot app is available, in which case your own app is optional (see Which OAuth app a connection uses). With your own app, the consent screen your client sees carries your brand, not ours. You build that app once and reuse it for every client.
Two HubSpot accounts are involved, doing different jobs:
| Account | Who owns it | What it holds |
|---|---|---|
| Your agency's HubSpot account | You | The OAuth app. No CRM data. |
| The client's portal | The client | The contacts, deals, and tickets workflows act on |
A free HubSpot account is enough to host the app.
HubSpot removed the browser form for creating OAuth apps on 23 June 2026, and offers no API for it. The CLI is now the only way to obtain a client ID and secret. You run it once, when you set your agency up — never per client, and your clients never run it.
Create the app with the HubSpot CLI
Install the CLI with
npm i -g @hubspot/cli, authenticate against your agency's HubSpot account withhs init, then runhs project createand choose OAuth as the authentication method. Upload it withhs project upload.Add the redirect URL
Set the app's
redirectUrlsinapp-hsmeta.jsonto your TaskJuice callback URL, then runhs project uploadagain. Redirect URLs live in the project file, so they change by re-upload rather than in the browser.Copy the client ID and secret
In HubSpot, go to Development → Projects, open your project, click the app under Project Components, and open the Auth tab. Both values are under Client credentials.
Add your app keys in TaskJuice
Open Apps → HubSpot, paste the client ID and secret under Your own OAuth app, and select the scopes your workflows need from the table below.
Approve each client's portal
On the app's page in HubSpot, click Add approved account(s) and select the client portal. Repeat for each client you connect.
HubSpot caps a private-distribution OAuth app at 10 approved accounts — or 100 when the app is created in a HubSpot Solutions Partner portal. If you expect to pass either ceiling, create the app in your Solutions Partner portal from the start; moving an app between portals later means re-creating it and re-authorizing every client.
HubSpot's service keys are not an alternative here. They are single-account credentials that cannot authenticate webhooks, so they cannot drive the triggers below.
Connect a HubSpot account
HubSpot connects through OAuth 2.0. There is no API key or secret to paste. The person who authorizes must be a HubSpot super admin, because HubSpot only lets super admins approve a connected app.
Add the connection from the node
Open a workflow, add any HubSpot trigger or action, and connect the account from the node's connection field.
Sign in to HubSpot as a super admin
Sign in and pick the HubSpot account (portal) to connect.
Approve the requested permissions
Approve the scopes HubSpot lists. Your agency's OAuth app decides which of the scopes below are requested.
For the full connect flow that applies to every OAuth app, see Connect an account.
Scopes
HubSpot scopes are granted per connection by the agency's own OAuth app. Request only what your workflows use — each action below names the scope it needs.
| Surface | Scopes |
|---|---|
| Contacts, companies, deals, tickets | crm.objects.<type>.read / .write, tickets |
| Products, line items, commerce objects | crm.objects.products.*, crm.objects.line_items.*, e-commerce |
| Custom objects | crm.objects.custom.read / .write, crm.schemas.custom.read |
| Property schemas | crm.schemas.<type>.read |
| Owners | crm.objects.owners.read |
| Lists | crm.lists.read / crm.lists.write |
| Conversations | conversations.read |
| Forms | forms |
| Workflows | automation |
| Files | files, files.write |
| Subscription preferences | communication_preferences.read_write |
| Custom behavioral events | analytics.behavioral_events.send |
| Blog posts | content |
To disconnect, remove the connection in TaskJuice. A HubSpot super admin can also remove the connected app from HubSpot's connected-apps settings.
Triggers
Real-time webhook triggers
HubSpot delivers these as webhooks, so a run starts as soon as the event happens. HubSpot batches events into a single POST; TaskJuice fans that batch out to one run per event.
HubSpot webhook payloads are minimal — they carry the record ID and, for property changes, the changed property name and value, but not the record. Pair any trigger with a Get Record action to hydrate the full object.
| Trigger | Slug | HubSpot subscription type |
|---|---|---|
| Contact Created | hubspot/contact-created | contact.creation |
| Contact Deleted | hubspot/contact-deleted | contact.deletion |
| Contact Merged | hubspot/contact-merged | contact.merge |
| Contact Restored | hubspot/contact-restored | contact.restore |
| Contact Association Changed | hubspot/contact-association-changed | contact.associationChange |
| Contact Privacy Deleted | hubspot/contact-privacy-deleted | contact.privacyDeletion |
| Company Created | hubspot/company-created | company.creation |
| Company Deleted | hubspot/company-deleted | company.deletion |
| Company Merged | hubspot/company-merged | company.merge |
| Company Restored | hubspot/company-restored | company.restore |
| Company Association Changed | hubspot/company-association-changed | company.associationChange |
| Deal Created | hubspot/deal-created | deal.creation |
| Deal Stage Changed | hubspot/deal-stage-changed | deal.propertyChange + dealstage |
| Deal Deleted | hubspot/deal-deleted | deal.deletion |
| Deal Merged | hubspot/deal-merged | deal.merge |
| Deal Restored | hubspot/deal-restored | deal.restore |
| Deal Association Changed | hubspot/deal-association-changed | deal.associationChange |
| Ticket Created | hubspot/ticket-created | ticket.creation |
| Ticket Deleted | hubspot/ticket-deleted | ticket.deletion |
| Ticket Merged | hubspot/ticket-merged | ticket.merge |
| Ticket Restored | hubspot/ticket-restored | ticket.restore |
| Ticket Association Changed | hubspot/ticket-association-changed | ticket.associationChange |
| Product Created | hubspot/product-created | product.creation |
| Product Deleted | hubspot/product-deleted | product.deletion |
| Product Merged | hubspot/product-merged | product.merge |
| Product Restored | hubspot/product-restored | product.restore |
| Line Item Created | hubspot/line-item-created | line_item.creation |
| Line Item Deleted | hubspot/line-item-deleted | line_item.deletion |
| Line Item Merged | hubspot/line-item-merged | line_item.merge |
| Line Item Restored | hubspot/line-item-restored | line_item.restore |
| Line Item Association Changed | hubspot/line-item-association-changed | line_item.associationChange |
| Conversation Started | hubspot/conversation-created | conversation.creation |
| Conversation Message Received | hubspot/conversation-new-message | conversation.newMessage |
| Conversation Deleted | hubspot/conversation-deleted | conversation.deletion |
| Conversation Privacy Deleted | hubspot/conversation-privacy-deleted | conversation.privacyDeletion |
A deal moving between pipeline stages fires Deal Stage Changed in real time. To react to any other field changing, use the Updated Contact or Updated Record polling trigger below.
Polling triggers
| Trigger | Slug | Checks for |
|---|---|---|
| New Form Submission | hubspot/new-form-submission | New submissions on a selected form. |
| New Contact in List | hubspot/new-contact-in-list | Contacts newly added to a list, including active (dynamic) lists. |
| New Engagement | hubspot/new-engagement | Engagements (notes, calls, emails, meetings, tasks) created after the workflow is first published. Editing an older one does not fire it. |
| New Record | hubspot/new-record | Newly created records of any object type — including custom objects. |
| Updated Contact | hubspot/updated-contact | Recently modified contacts. |
| Updated Record | hubspot/updated-record | Recently modified records of any other object type. |
| New Blog Post | hubspot/new-blog-post | Newly created blog posts. |
New Record and Updated Record are the only way to trigger on a custom object. HubSpot publishes no webhook subscription type per custom object, so a poll over the search endpoint is the supported path. They are also useful when a property-change webhook would be too noisy. Contacts are watched by Updated Contact rather than Updated Record, because HubSpot stores a contact's modified date under a different property name than every other object.
Each cycle emits one run carrying an items array. Add a Loop node over items to process each record individually; leave it out for digest-style workflows.
Trigger output
Webhook triggers share a common core, with per-family additions.
| Field | Type | Present on | Description |
|---|---|---|---|
objectId | number | All except merge and association triggers | HubSpot record ID |
eventId | number | All | Unique event ID for idempotency |
portalId | number | All | HubSpot portal/account ID |
subscriptionType | string | All | The HubSpot event type |
occurredAt | number | All | Unix timestamp in milliseconds |
changeSource | string | All | What caused the change |
attemptNumber | number | All | Delivery attempt number |
propertyName | string | Deal Stage Changed | Changed property name |
propertyValue | string | Deal Stage Changed | Changed property value |
Merge triggers replace objectId with primaryObjectId, mergedObjectIds, newObjectId, and numberOfPropertiesMoved. Association Changed triggers replace it with associationType, fromObjectId, toObjectId, associationRemoved, and isPrimaryAssociation — read associationRemoved to tell an add from a remove.
Actions
Records
The six generic Record actions each take an objectType and work against any HubSpot object type.
| Action | Slug | What it does |
|---|---|---|
| Create Record | hubspot/create-record | Creates a record of any object type. |
| Update Record | hubspot/update-record | Updates an existing record by ID. |
| Get Record | hubspot/get-record | Retrieves a single record by ID. |
| Delete Record | hubspot/delete-record | Deletes a record by ID. |
| Search Records | hubspot/search-records | Searches with filters, sorting, and property selection. |
| List Records | hubspot/list-records | Lists records of one object type. |
| Upsert Contact | hubspot/upsert-contact | Creates or updates contacts in batch by a unique property. |
| Merge Records | hubspot/merge-records | Merges two records. Contacts, companies, deals, tickets, orders, appointments, courses, services, and projects only. |
Associations, properties, and metadata
| Action | Slug | What it does |
|---|---|---|
| Create Association | hubspot/create-association | Associates two CRM records by association category and type ID. |
| Remove Association | hubspot/remove-association | Removes an association between two records. |
| List Associations | hubspot/list-associations | Lists every record of one type associated with a record (max 500). |
| List Properties | hubspot/list-properties | Lists a object type's properties — use it to discover internal names. |
| List Pipelines | hubspot/list-pipelines | Lists pipelines and their stages for an object type. |
| Get Owner | hubspot/get-owner | Retrieves an owner by ID. |
| List Owners | hubspot/list-owners | Lists owners; filter by email to resolve an owner from an address. |
Engagements
| Action | Slug | What it does |
|---|---|---|
| Create Note | hubspot/create-note | Creates a note engagement. |
| Create Task | hubspot/create-task | Creates a task. |
| Log Call | hubspot/create-call | Logs a call activity. |
| Log Meeting | hubspot/create-meeting | Logs a meeting activity. |
| Log Email | hubspot/create-email | Logs an email activity. |
All five require hs_timestamp.
Lists and marketing
| Action | Slug | What it does |
|---|---|---|
| Create List | hubspot/create-list | Creates a manual, snapshot, or dynamic list. |
| Get List | hubspot/get-list | Fetches a list by ILS list ID. |
| Search Lists | hubspot/search-lists | Searches lists by name, or pages through all of them. |
| Add Contact to List | hubspot/add-contact-to-list | Adds records to a list. |
| Remove Contact from List | hubspot/remove-contact-from-list | Removes records from a list. |
| Submit Form | hubspot/submit-form | Submits data to a HubSpot form. |
| Get Form | hubspot/get-form | Reads a form definition and its fields. |
| Enroll Contact in Workflow | hubspot/enroll-contact-in-workflow | Enrols a contact by email in a workflow picked from the account. |
| Subscribe Contact to Email Type | hubspot/subscribe-contact-to-email | Opts a contact into a subscription type. |
| Unsubscribe Contact from Email Type | hubspot/unsubscribe-contact-from-email | Opts a contact out of a subscription type. |
| Send Custom Event | hubspot/send-custom-event | Records a custom behavioral event occurrence. |
| List Email Events | hubspot/list-email-events | Lists marketing-email sends, opens, clicks and bounces in a time window. |
| Get Email Subscriptions Timeline | hubspot/get-email-subscriptions-timeline | Lists opt-in/opt-out changes across the account. |
| Create Blog Post | hubspot/create-blog-post | Creates a blog post, as a draft unless published. |
| List Blog Posts | hubspot/list-blog-posts | Lists blog posts, filterable by created/updated time. |
Files
| Action | Slug | What it does |
|---|---|---|
| Upload File | hubspot/upload-file | Uploads a file reference from an earlier step. |
| Import File from URL | hubspot/import-file-from-url | Imports a file from a public URL. Returns a task, not a file. |
| Get File Signed URL | hubspot/get-file-signed-url | Gets a temporary URL for a private file. |
Use Upload File when an earlier step produced a file, and Import File from URL when the file already lives at a public address.
Dynamic pickers
These fields populate from the connected account rather than being typed by hand: deal pipeline, owner, form, list, subscription type, and workflow. Every one also has an expression toggle, so you can bind a value from an earlier step instead of picking.
There is no record picker for contacts, companies, deals, or tickets — supply record IDs from earlier steps or from Search Records.
Errors
Actions map HubSpot's HTTP responses to TaskJuice error codes. Each action times out after 15 seconds and retries up to 2 times on retryable errors with exponential backoff.
| Error code | HTTP | Retryable | What it means and what to do |
|---|---|---|---|
VALIDATION_ERROR | 400 | No | HubSpot rejected the request. Check property names and required fields. |
AUTH_EXPIRED | 401 | No | The connection is no longer valid. Reauthorize the HubSpot connection, then resume affected runs. |
MISSING_SCOPES | 403 | No | The connection lacks a required permission. Reconnect and grant it. |
OBJECT_NOT_FOUND | 404 | No | The record, list, form, or owner ID does not exist. |
OBJECT_ALREADY_EXISTS | 409 | No | A conflicting record already exists. |
RATE_LIMITED | 429 | Yes | HubSpot throttled the request. TaskJuice retries with backoff. |
PROVIDER_ERROR | 500, 502, 503, 504 | Yes | HubSpot had a server error or the gateway timed out. TaskJuice retries. |
HubSpot access tokens are short-lived and TaskJuice refreshes them for you. If a token cannot be refreshed — for example the connected app was removed in HubSpot — the next action returns AUTH_EXPIRED.
Known limitations
- Webhook payloads are minimal. Triggers deliver identifiers, not records. Pair any trigger with Get Record to hydrate the object.
- Webhook subscriptions are configured on the HubSpot app, not per workspace. Your agency subscribes to event types once on its own HubSpot app; TaskJuice routes each delivery to the right workspace by
portalId. - Search has a separate, low rate limit of roughly 4 requests per second. The non-search CRM endpoints use HubSpot's higher standard limit.
- Tier-gated surfaces. Blog posts need Content Hub or Marketing Hub, custom behavioral events need Enterprise, and custom objects need Enterprise. Calls against a tier the portal does not have return
MISSING_SCOPES. - Merge is narrower than the record actions. Only the object types listed under Merge Records expose a merge endpoint.
- Upsert is contacts-only. Other object types use Create Record or Update Record.
- No batch record actions. Create, update, and delete act on one record per call.
- Email-event reads are window-based, not a trigger.
GET /email/public/v1/eventsdocuments no sort order and offers no sort parameter, so any watermark over it would silently drop events. List Email Events takes an explicit start/end window instead, which is correct regardless of ordering. - Social media publishing is not available. HubSpot has withdrawn the Broadcast channel-listing endpoint, so there is no supported way to choose a social channel to publish to.
- Custom objects have no schema picker. Bind the
objectTypeIdthrough the expression toggle and supply property names yourself — List Properties will tell you what they are. - One agency app reaches 10 client portals. HubSpot caps a private-distribution OAuth app at 10 approved accounts, or 100 when the app lives in a Solutions Partner portal. Listing on HubSpot's App Marketplace lifts the cap but requires the app to be publicly distributed.
- No real-time "… Updated" webhook triggers. HubSpot no longer accepts a "any property changed" webhook subscription — it requires naming the property — so there are no real-time Contact/Company/Deal/Ticket Updated triggers. Use the Updated Contact or Updated Record polling trigger to react to changes, or Deal Stage Changed for pipeline moves.
- Association events are chatty. One association call can produce dozens of
associationChangedeliveries — HubSpot emits the reverse direction too, and a merge or delete cascades one event per association the record had. Dedupe oneventIdif you only want one run per change. - Products and line items cannot be merged. HubSpot returns
400 Merges are not supportedfor both, so there is no Product Merged or Line Item Merged trigger and Merge Records does not offer those types. - Enroll Contact in Workflow addresses the workflow's legacy ID, not the flow ID in the URL. HubSpot's enrollment endpoint only parses the older numeric workflow ID, which is not the ID shown in a workflow's browser URL. The picker resolves it for you; if you bind an expression instead, supply the
workflowIdfrom HubSpot's Workflows v3 API, not the URL. - Custom events need an active event definition. Send Custom Event answers
400 No active event definition founduntil the event is defined under Reports → Data Management → Custom events (or via the event-definitions API), and the connection's app needs theanalytics.behavioral_events.sendscope. - Blog posts need a blog. Create Blog Post takes the blog (
contentGroupId) from the picker; a portal with no blog set up has nothing to pick. - Triggers and actions are in beta. Behavior may change as the integration matures.