Menu
Connectors
HubSpot
Work with HubSpot CRM — contacts, companies, deals, tickets, products, line items, quotes, engagements, associations, pipelines, lists, files, and more — through its REST API. Covers the shared request/response model and every module's endpoints.
On this page
Connecting HubSpot
Every action needs a HubSpot connection configured with an Access Token — a HubSpot Private App access token (Settings → Integrations → Private Apps → your app → Auth), or an OAuth access token. It's sent as a Bearer token on every request automatically; there's no per-action auth configuration.
The Private App must be granted the scopes for the objects you use (e.g. crm.objects.contacts.read / crm.objects.contacts.write). A request for an object whose scope is missing comes back as a 403 — see Error handling below for how that surfaces.
All requests go to https://api.hubapi.com.
How HubSpot actions are structured
Each action is one HubSpot API operation. They're grouped in the action picker by module (Contacts, Companies, Deals, …) — the sections below match those modules. Action names are generated from the HubSpot operation, so they read like "List", "Create", "Read", "Update", "Archive", "Search", "Create a batch of contacts" rather than hand-written phrases — search the picker by the object plus the verb.
Every action exposes the same shape of configuration fields, assembled onto the request for you:
- Path parameters — top-level fields named exactly as in the endpoint path (e.g. contactId, dealId, objectType). Required whenever the path has that segment; interpolated into the URL.
- Query parameters — top-level fields for each query parameter the operation declares. Common ones on read operations: limit (page size), after (paging cursor), properties (comma-separated property names to return), associations (comma-separated object types whose associated IDs to include), archived (include archived records).
- Request Body — for
POST/PATCH/PUToperations, a structured field with one input per property the spec declares (for CRM objects this is an inputs array for batch operations, or a properties object for single-record operations). - Override Request Body + Request Body (Raw) — a toggle and a free-text field: turn the toggle on to bypass the structured fields and send raw JSON you write yourself. Use this for HubSpot request shapes the structured form doesn't capture cleanly (nested filter groups, association payloads).
- Forward All Headers — forward headers from the workflow's incoming request (off by default).
- Additional Headers / Additional Query Parameters — key/value escape hatches for anything not already covered; these override declared values with the same name.
Reading the response
Every action's result lands at {{<stepReference>.response...}} (see Step Reference), in the same envelope the HTTP Connector uses:
{{<stepReference>.response.status}}— the HTTP status code.{{<stepReference>.response.statusText}}— the HTTP status text.{{<stepReference>.response.headers.<header-name>}}— a response header.{{<stepReference>.response.data.<field>}}— the parsed HubSpot response body.
HubSpot's own body shapes are consistent across the CRM object modules:
- A single record (read / create / update / merge) —
{{<stepReference>.response.data.id}},{{<stepReference>.response.data.properties.<name>}}(e.g.{{<stepReference>.response.data.properties.email}}),{{<stepReference>.response.data.createdAt}},{{<stepReference>.response.data.archived}}, and{{<stepReference>.response.data.associations}}when associations were requested. So a step named "Read" (referenceread) resolves a contact's email at{{read.response.data.properties.email}}. - A list (the "List" / getPage operation) —
{{<stepReference>.response.data.results}}is the array (e.g.{{<stepReference>.response.data.results[0].id}}), and{{<stepReference>.response.data.paging.next.after}}is the cursor to pass back as after for the next page (absent on the last page). - A search —
{{<stepReference>.response.data.results}}plus{{<stepReference>.response.data.total}}(total matching count) and{{<stepReference>.response.data.paging.next.after}}. - A batch operation (create / read / update / upsert) —
{{<stepReference>.response.data.results}}is the array of affected records, and{{<stepReference>.response.data.status}}isCOMPLETE(HTTP200/201) or, on a partial failure (HTTP207), the response also carries a{{<stepReference>.response.data.errors}}array — check{{<stepReference>.response.status}}to tell the two apart. - An archive / delete / GDPR-delete — HubSpot returns
204 No Contentwith an empty body, so there's nothing under.response.data; confirm success with{{<stepReference>.response.status}}being204.
Modules outside the CRM object pattern (Associations, Pipelines, Lists, Files, Meetings, Webhooks, Imports, Exports) return HubSpot's own payload for that endpoint at {{<stepReference>.response.data.<field>}} — the module sections below note the shape, and HubSpot's API reference has the exact fields.
Working with properties
HubSpot records carry their data in a properties object, not top-level fields. When creating or updating, the request body's properties is a flat map of HubSpot property names to values, e.g. {"email": "user@example.com", "firstname": "Ada", "lifecyclestage": "lead"}. When reading, only a default set of properties comes back unless you name them: set the properties query parameter to a comma-separated list (e.g. email,firstname,lastname,hs_lead_status).
Searching
The Search action on each CRM object module takes a request body with filterGroups (an array — filters within one group are AND'd, groups are OR'd), sorts, properties, limit, and after. This nested shape is easiest to build by turning on Override Request Body and writing the JSON directly, e.g.:
{
"filterGroups": [
{ "filters": [ { "propertyName": "email", "operator": "EQ", "value": "user@example.com" } ] }
],
"properties": ["email", "firstname", "lastname"],
"limit": 10
}
Error handling
A non-2xx response from HubSpot does not fail the step — it's returned as a normal result with the real status code and HubSpot's error body at {{<stepReference>.response.data}} (typically {{<stepReference>.response.data.message}} and {{<stepReference>.response.data.category}}), so you can branch on {{<stepReference>.response.status}} in the workflow. Common cases: 401 (bad or expired token), 403 (missing Private App scope), 404 (record archived or wrong ID), 429 (rate limited — HubSpot's Retry-After is at {{<stepReference>.response.headers.retry-after}}).
Standard CRM object actions
Twelve modules — Contacts, Companies, Deals, Tickets, Products, Line Items, Quotes, Notes, Calls, Emails, Tasks, and Custom Objects — expose the same set of operations against /crm/v3/objects/<object>:
- List —
GET /crm/v3/objects/<object>. Query:limit,after,properties,associations,archived. - Create —
POST /crm/v3/objects/<object>. Body:{ "properties": { … }, "associations": [ … ] }. - Read —
GET /crm/v3/objects/<object>/{<object>Id}. Query:properties,associations,archived. - Update —
PATCH /crm/v3/objects/<object>/{<object>Id}. Body:{ "properties": { … } }. - Archive —
DELETE /crm/v3/objects/<object>/{<object>Id}. Returns204. - Search —
POST /crm/v3/objects/<object>/search. See Searching above. - Batch Create / Read / Update / Upsert —
POST /crm/v3/objects/<object>/batch/{create|read|update|upsert}. Body:{ "inputs": [ … ] }(each input is a{ "properties": … }, a{ "id": … }, or an{ "id": …, "properties": … }depending on the operation). - Batch Archive —
POST /crm/v3/objects/<object>/batch/archive. Body:{ "inputs": [ { "id": "…" } ] }. Returns204.
Each module's section below only calls out what's different from this list (the object's path segment, extra operations like Merge, or a non-standard object-type id).
Contacts
Object: contacts. Path segment /crm/v3/objects/contacts, record path parameter contactId. Has all the standard CRM object actions described above, plus:
- Merge —
POST /crm/v3/objects/contacts/merge. Body:{ "primaryObjectId": "…", "objectIdToMerge": "…" }. Returns the surviving contact record. - GDPR delete —
POST /crm/v3/objects/contacts/gdpr-delete. Body:{ "objectId": "…" }(oridProperty+objectId). Permanently erases the contact; returns204. This is irreversible — unlike Archive, which is a soft delete.
Example
To create a contact: turn on Override Request Body and send { "properties": { "email": "ada@example.com", "firstname": "Ada", "lastname": "Lovelace" } }. The new contact's ID is at {{<stepReference>.response.data.id}}.
Companies
Object: companies. Path segment /crm/v3/objects/companies, record path parameter companyId. Has all the standard CRM object actions, plus Merge (POST /crm/v3/objects/companies/merge).
Deals
Object: deals, addressed by its object-type id — the path segment is /crm/v3/objects/0-3, not /crm/v3/objects/deals. Record path parameter dealId. Has all the standard CRM object actions, plus Merge (POST /crm/v3/objects/0-3/merge). Deal stage and pipeline are properties (dealstage, pipeline) — use the Pipelines module to look up valid stage ids.
Tickets
Object: tickets. Path segment /crm/v3/objects/tickets, record path parameter ticketId. Has all the standard CRM object actions, plus Merge (POST /crm/v3/objects/tickets/merge).
Products
Object: products (the product library). Path segment /crm/v3/objects/products, record path parameter productId. Standard CRM object actions only (no Merge). Key properties: name, price, hs_sku.
Line Items
Object: line_items. Path segment /crm/v3/objects/line_items, record path parameter lineItemId. Standard CRM object actions only. A line item is normally created then associated with a deal (see Associations); key properties: name, quantity, price, hs_product_id.
Quotes
Object: quotes. Path segment /crm/v3/objects/quotes, record path parameter quoteId. Standard CRM object actions only.
Notes
Object: notes (an engagement). Path segment /crm/v3/objects/notes, record path parameter noteId. Standard CRM object actions only. Body content is the hs_note_body property; hs_timestamp is required on create. Associate the note with the record it's about via Associations.
Calls
Object: calls (an engagement). Path segment /crm/v3/objects/calls, record path parameter callId. Standard CRM object actions only. Key properties: hs_call_title, hs_call_body, hs_call_direction, hs_call_duration, hs_timestamp.
Emails
Object: emails (a logged email engagement — not the Marketing Email API). Path segment /crm/v3/objects/emails, record path parameter emailId. Standard CRM object actions only. Key properties: hs_email_subject, hs_email_text, hs_email_direction, hs_timestamp.
Meetings
Object: meetings — but this module uses HubSpot's Scheduler API (/scheduler/v3/meetings/...), which is a different shape from the CRM object modules:
- Create a new calendar meeting event —
POST /scheduler/v3/meetings/calendar. - Get meeting scheduling pages —
GET /scheduler/v3/meetings/meeting-links. Returns{{<stepReference>.response.data.results}}. - Book a meeting —
POST /scheduler/v3/meetings/meeting-links/book. - Get the availability for a meeting —
GET /scheduler/v3/meetings/meeting-links/book/availability-page/{slug}. Path parameter slug. - List booking information —
GET /scheduler/v3/meetings/meeting-links/book/{slug}. Path parameter slug.
To read or write meeting engagement records on a contact/deal timeline instead, use the Custom Objects module with object type meetings.
Custom Objects
The standard CRM object actions, parameterized: the path segment is /crm/v3/objects/{objectType} with a path parameter objectType. Set objectType to a custom object's fully-qualified name (e.g. p12345_projects) or to any standard object name — including ones without their own module, such as tickets, meetings, or feedback_submissions. Record path parameter objectId. Has Merge too (POST /crm/v3/objects/{objectType}/merge).
Associations
Links records of one type to records of another (contact ↔ deal, line item ↔ deal, note ↔ company, …). This module uses HubSpot's v4 associations batch API and has three actions, all POST /crm/v3/associations/{fromObjectType}/{toObjectType}/batch/{...} with path parameters fromObjectType and toObjectType:
- Associate records (labelled) —
.../batch/create. Body:{ "inputs": [ { "from": { "id": "…" }, "to": { "id": "…" }, "types": [ { "associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 1 } ] } ] }. - Retrieve associations —
.../batch/read. Body:{ "inputs": [ { "id": "<fromRecordId>" } ] }. Returns{{<stepReference>.response.data.results}}, each entry pairing afromid with its associatedtoids. - Remove associations —
.../batch/archive. Body: sameinputsshape as create. Returns204.
Use the paired object types in path order, e.g. fromObjectType contacts, toObjectType deals. associationTypeId values come from HubSpot's association-schema endpoints or its docs (the default contact→deal type is 3, deal→contact is 4).
Owners
Read-only. One action:
- Retrieve a specific owner by ID —
GET /crm/v3/owners/{ownerId}. Path parameter ownerId. Returns{{<stepReference>.response.data.id}},{{<stepReference>.response.data.email}},{{<stepReference>.response.data.firstName}},{{<stepReference>.response.data.lastName}},{{<stepReference>.response.data.userId}}.
Owner ids are what CRM properties like hubspot_owner_id hold, so this resolves an assignee to a name/email.
Pipelines
Manage pipelines and their stages for a CRM object type. All paths start /crm/v3/pipelines/{objectType} with path parameter objectType (e.g. deals, tickets):
- Retrieve all pipelines —
GET /crm/v3/pipelines/{objectType}. Returns{{<stepReference>.response.data.results}}. - Create a pipeline —
POST /crm/v3/pipelines/{objectType}. - Return a pipeline by ID —
GET /crm/v3/pipelines/{objectType}/{pipelineId}. Path parameter pipelineId. - Replace a pipeline —
PUT /crm/v3/pipelines/{objectType}/{pipelineId}. - Delete a pipeline —
DELETE /crm/v3/pipelines/{objectType}/{pipelineId}. - Return an audit of all changes to the pipeline —
GET /crm/v3/pipelines/{objectType}/{pipelineId}/audit. - Return all stages of a pipeline —
GET /crm/v3/pipelines/{objectType}/{pipelineId}/stages. - Create a pipeline stage —
POST /crm/v3/pipelines/{objectType}/{pipelineId}/stages. - Return a pipeline stage by ID / Replace a pipeline stage / Delete a pipeline stage —
GET/PUT/DELETE /crm/v3/pipelines/{objectType}/{pipelineId}/stages/{stageId}. Path parameter stageId.
Each stage object has an id and a label — a stage id is what a deal's dealstage property must be set to.
Lists
Manage HubSpot lists (formerly "contact lists"), their folders, and their membership. 26 actions against /crm/v3/lists/.... The frequently-used ones:
- Fetch List by ID —
GET /crm/v3/lists/{listId}. Path parameter listId. Returns{{<stepReference>.response.data.list}}. - Search Lists —
POST /crm/v3/lists/search. Body:{ "query": "…", "listIds": [...], "offset": 0, "count": 100 }. Returns{{<stepReference>.response.data.lists}}and{{<stepReference>.response.data.total}}. - Retrieve List by Name —
GET /crm/v3/lists/object-type-id/{objectTypeId}/name/{listName}. Path parameters objectTypeId (e.g.0-1for contacts) and listName. - Fetch List Memberships Ordered by ID —
GET /crm/v3/lists/{listId}/memberships. Returns{{<stepReference>.response.data.results}}(record ids) and{{<stepReference>.response.data.paging}}. - Add Records to a List / Remove Records from a List —
PUT /crm/v3/lists/{listId}/memberships/add/.../remove. Body: a JSON array of record ids. Only works onMANUALandSNAPSHOTlists, notDYNAMICones. - Add and/or Remove Records from a List —
PUT /crm/v3/lists/{listId}/memberships/add-and-remove. - Get lists record is member of —
GET /crm/v3/lists/records/{objectTypeId}/{recordId}/memberships. Path parameters objectTypeId and recordId. - Create a List is not a single operation here — lists are created via
POST /crm/v3/lists/on HubSpot's side; this module's list-writing operations focus on folders, membership, filters (update-list-filters), renaming, restoring, and static-conversion scheduling.
The remaining actions cover list folders (create, delete, rename, move), idmapping (translate legacy numeric list ids to modern ids), and list lifecycle (restore a deleted list, schedule/cancel conversion of a dynamic list to static).
Files
Manage files and folders in the HubSpot File Manager. 20 actions across /files/v3/files/... and /files/v3/folders/...:
- Upload file —
POST /files/v3/files. This is amultipart/form-dataupload; the structured form doesn't build multipart bodies, so this action is only usable when you already have the file content available to pass through — most workflows import by URL instead (next item). - Import file from URL —
POST /files/v3/files/import-from-url/async. Body includes the sourceurl,folderPath(orfolderId),fileName, andaccess(PUBLIC_INDEXABLE,PUBLIC_NOT_INDEXABLE,PRIVATE). Returns a task id. - Check import status —
GET /files/v3/files/import-from-url/async/tasks/{taskId}/status. Path parameter taskId. - Search files —
GET /files/v3/files/search. Query parameters forname,extension,type,parentFolderIds, etc. Returns{{<stepReference>.response.data.results}}. - Retrieve file by ID / by path —
GET /files/v3/files/{fileId}/GET /files/v3/files/stat/{path}. Path parameter fileId or path. - Get signed URL to access private file —
GET /files/v3/files/{fileId}/signed-url. Returns{{<stepReference>.response.data.url}}(time-limited). - Replace file / Update file properties / Delete file by ID / GDPR-delete file —
PUT/PATCH/DELETE /files/v3/files/{fileId}andDELETE /files/v3/files/{fileId}/gdpr-delete. - Folder operations — create, search, retrieve by id or path, update properties, delete, and an async recursive property update with its own status endpoint, all under
/files/v3/folders/....
Webhooks
Configure webhook subscriptions for a HubSpot app (not for a workflow — use a workflow's own webhook trigger for that). All paths are /webhooks/v3/{appId}/... with path parameter appId:
- Read webhook settings / Update webhook settings / Delete webhook settings —
GET/PUT/DELETE /webhooks/v3/{appId}/settings. Settings hold the target URL and throttling. - Read event subscriptions —
GET /webhooks/v3/{appId}/subscriptions. Returns{{<stepReference>.response.data.results}}. - Create an event subscription —
POST /webhooks/v3/{appId}/subscriptions. Body:{ "eventType": "contact.propertyChange", "propertyName": "email", "active": true }. - Batch create event subscriptions —
POST /webhooks/v3/{appId}/subscriptions/batch/update. - Read / Update / Delete an event subscription —
GET/PATCH/DELETE /webhooks/v3/{appId}/subscriptions/{subscriptionId}. Path parameter subscriptionId.
Imports
Track CRM record imports (the import itself is started in the HubSpot UI or via the import-start endpoint). Three actions:
- Get the information on any import —
GET /crm/v3/imports/{importId}. Path parameter importId. Returns{{<stepReference>.response.data.state}}(STARTED,PROCESSING,DONE,FAILED,CANCELED) and{{<stepReference>.response.data.metadata}}. - Cancel an active import —
POST /crm/v3/imports/{importId}/cancel. - Retrieve errors for a specific import —
GET /crm/v3/imports/{importId}/errors. Returns{{<stepReference>.response.data.results}}.
Exports
Run and retrieve CRM data exports. Three actions:
- Start an export —
POST /crm/v3/exports/export/async. Body describes the object type, format (CSV,XLSX), properties, and any filters. Returns{{<stepReference>.response.data.id}}. - Get Status of Export Including URL to Download —
GET /crm/v3/exports/export/async/tasks/{taskId}/status. Path parameter taskId. When{{<stepReference>.response.data.status}}isCOMPLETE, the download link is at{{<stepReference>.response.data.result}}(a time-limited URL). - Retrieve details of a specific export by its unique ID —
GET /crm/v3/exports/export/{exportId}. Path parameter exportId.
Also applies here
Step Name
What it's for
Every step in a workflow gets a name — either one you set or a default based on the connector and action (e.g. "Get Order Details", "Send Welcome Email"). It's shown throughout the UI and in your execution history, and it's also the source for the step's Reference — a camelCase identifier auto-generated from the name (e.g. "Get Order Details" → getOrderDetails) — which is what you actually use in {{...}} expressions to read this step's output from later steps. See Step Reference.
Rules
- Must be at least 2 characters, and 50 characters or fewer.
- Must be unique within the workflow — reusing a name that's already taken will be rejected, with a suggested alternative (e.g.
"Get Order Details 2"). - Can't be empty.
Tips
- Prefer a descriptive, human-readable name over a generic one — "Get Order Details" is easier to work with later than "HTTP Request 2", especially once a workflow has a dozen steps.
- Renaming a step updates every reference to it elsewhere in the workflow automatically.
Step Reference
Syntax
Any input field can reference earlier data using {{expression}}. The expression is evaluated as JSONata — so simple dot-paths and more advanced queries (filters, functions) both work.
Referencing a step's output
Use the step's Reference — a camelCase identifier auto-generated from its Name (e.g. "Get Order Details" → getOrderDetails), shown read-only wherever the step's fields are configured — followed by the field path. Elsewhere in these docs this general pattern is written as {{<stepReference>.field.path}}:
{{getOrderDetails.response.data.id}}
{{getOrderDetails.response.status}}
The raw display name won't work here even though it's what you see in the UI — {{Get Order Details.response.data.id}} isn't valid, since a bare name containing spaces isn't a single JSONata identifier. Always use the camelCase Reference.
You can also reference steps by position instead of by reference:
{{steps[0].response.data.id}}
Referencing trigger data
{{trigger.headers.authorization}}
{{trigger.body.customerId}}
{{trigger.query.page}}
{{trigger.params.orderId}}
{{trigger.method}}
{{trigger.path}}
Referencing workflow variables
{{variables.myVariable}}
See Variables for the full list of variable types and more examples, including connection-type variables.
Referencing runtime variables
A separate, mutable namespace written by the Variable connector while a run is in progress — not the same as the workflow-level variables above:
{{runtimeVariables.myVariable}}
Notes
- If an expression can't be resolved (a typo in a step name, a field that doesn't exist), it resolves to an empty string rather than failing the workflow — check your execution history if a value comes through blank.
- Object values are automatically JSON-stringified when interpolated into a string field.
Execution Settings
Fire-and-forget
When enabled, the workflow doesn't wait for this step to complete before moving to the next one. Use it for steps whose result nothing downstream depends on — logging, analytics, notifications — so they don't add latency to the steps that matter.
Continue on error
When enabled, a failure in this step doesn't stop the workflow — execution continues to the next step. The failure is still recorded in the execution history; this just controls whether it's fatal.
Combine the two for steps that are genuinely optional to the outcome: fire-and-forget so they don't add latency, continue-on-error so a failure in them (e.g. an analytics endpoint being briefly down) doesn't take down an otherwise-successful workflow run.
Caching
What it does
When enabled, a step's result is cached for a configurable TTL (time-to-live, in seconds). If the step runs again with the same effective cache key before the TTL expires, the cached result is returned instead of re-running the step.
Configuring it
- Enabled — turn caching on or off for this step.
- TTL — how long (in seconds) a cached result stays valid.
- Cache key template — an expression (supporting the same
{{...}}step reference syntax used elsewhere) that determines what counts as "the same call". By default this is based on the step's resolved input; a custom template lets you cache more narrowly or broadly than that.
When to use it
Good candidates are steps that call something slow or rate-limited but return the same answer for the same input within a short window — a lookup against a rarely-changing external system, for example. Skip it for steps whose result must always be fresh (anything involving live inventory, pricing, or payment state).
Locking
What it does
When enabled, only one execution of this step (for a given lock key) can run at a time. If a second execution tries to run the same step while a lock is held, it waits until the lock is released or the hold period elapses.
Configuring it
- Enabled — turn locking on or off for this step.
- Lock key — an expression (supporting the same
{{...}}step reference syntax used elsewhere) that determines what counts as "the same resource". By default the lock is scoped to the step itself; a custom key lets you lock per-customer, per-order, or any other identifier that needs serialized access. - Period — how long (in seconds) the lock is held before it's automatically released, in case an execution doesn't complete normally.
When to use it
Use it whenever concurrent executions could race on the same resource — e.g. two workflow runs both trying to update the same order's status at once. A lock key scoped to the order id ensures only one of them proceeds at a time.