younifyd
Menu

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 connecting, the shared request/response model, quirks, and common tasks.

Browse 219 HubSpot actions Associations · Calls · Companies · Contacts · Custom Objects · Deals · +15 more
On this page

What this connector is

HubSpot CRM through its REST API: contacts, companies, deals, tickets, products, line items, quotes, engagements (notes, calls, emails, tasks, meetings), associations, owners, pipelines, lists, files, webhooks, imports and exports. Actions are generated from HubSpot's own API specs, so each one maps to a single HubSpot operation.

Connecting HubSpot

Every action needs a HubSpot connection with an Access Token: a Private App token (Settings → Integrations → Private Apps → your app → Auth) or an OAuth access token. It is sent as a Bearer token on every request; there is no per-action auth setup. All requests go to https://api.hubapi.com (each action also has a Base URL field you can override).

Grant the Private App the scopes for the objects you use, for example crm.objects.contacts.read and crm.objects.contacts.write. A missing scope comes back as a 403 (see Error handling).

Your first call

  1. Add a step with the Contacts module's Create action and name it "Create Contact" (reference createContact).
  2. Pick your HubSpot connection and put this in the Request Body field:
json
{ "properties": { "email": "ada@example.com", "firstname": "Ada", "lastname": "Lovelace" } }
  1. Run the workflow and read the new contact's id with {{createContact.response.data.id}}.
  2. Pass that id as contactId to a later Read step.

Every action, with its parameters and response fields, is listed in the action reference.

How actions are organised

Actions are grouped by module in the picker: Contacts, Companies, Deals, Tickets, Products, Line Items, Quotes, Notes, Calls, Emails, Tasks, Meetings, Custom Objects, Associations, Owners, Pipelines, Lists, Files, Webhooks, Imports and Exports. Names come from the HubSpot operation ("List", "Create", "Read", "Update", "Archive", "Search", "Create a batch of contacts"), so search the picker by object plus verb.

Standard CRM object actions

Contacts, Companies, Deals, Tickets, Products, Line Items, Quotes, Notes, Calls, Emails, Tasks and Custom Objects share one set of operations against /crm/v3/objects/<object>: List, Create, Read, Update, Archive, Search, Batch Create / Read / Update / Upsert, and Batch Archive. Only the object's path segment and record path parameter (for example contactId, dealId) differ.

Quirks and exceptions

  • Deals use an object-type id in the path: /crm/v3/objects/0-3, not /crm/v3/objects/deals. The record path parameter is dealId. Deal stage and pipeline are the properties dealstage and pipeline; use the Pipelines module to look up valid stage ids (a stage's id is what dealstage must hold).
  • Merge exists on Contacts, Companies, Deals, Tickets and Custom Objects only (POST .../merge, body { "primaryObjectId": "...", "objectIdToMerge": "..." }). Products, Line Items, Quotes, Notes, Calls, Emails and Tasks have no Merge.
  • Contacts GDPR delete (POST /crm/v3/objects/contacts/gdpr-delete) permanently erases a contact and is irreversible. Archive is a soft delete.
  • Custom Objects is the standard set parameterised by an objectType path parameter. Use a custom object's fully-qualified name (for example p12345_projects) or any standard object name, including ones without their own module such as feedback_submissions. The record parameter is objectId.
  • Meetings is not a CRM object module. It uses the Scheduler API (/scheduler/v3/meetings/...): create a calendar meeting, list meeting links, book, get availability and list booking info (the last two take a slug). To read or write meeting engagement records on a timeline, use Custom Objects with objectType meetings.
  • Emails are logged email engagements, not the Marketing Email API.
  • Notes need hs_timestamp on create, with the text in hs_note_body. Line items are normally created and then associated with a deal.
  • Associations run on POST /crm/v3/associations/{fromObjectType}/{toObjectType}/batch/{create|read|archive}. Use the object types in path order (for example fromObjectType contacts, toObjectType deals). Get associationTypeId values from HubSpot's association-schema endpoints.
  • Lists: Add / Remove Records only work on MANUAL and SNAPSHOT lists, never DYNAMIC ones. Retrieve List by Name needs objectTypeId (for example 0-1 for contacts).
  • Files: Upload file is multipart/form-data, which the request body field can't build, so most workflows use Import file from URL and then Check import status with the returned task id. Get signed URL returns a time-limited link for private files.
  • Webhooks configure subscriptions for a HubSpot app (/webhooks/v3/{appId}/...), not for a workflow. Use a workflow's own webhook trigger for that.
  • Imports only track and cancel imports (state STARTED, PROCESSING, DONE, FAILED or CANCELED), and Exports start and poll exports.
  • Owners has one read action. Owner ids are what hubspot_owner_id holds.

Request fields

Every action assembles its request from these fields:

  • Path parameters — top-level fields named as in the endpoint path (contactId, objectType, ...), required wherever the path has that segment.
  • Query parameters — top-level fields. Common on reads: limit, after (paging cursor), properties (comma-separated property names), associations (comma-separated object types whose ids to include) and archived.
  • Request Body — one freeform field for POST / PATCH / PUT: JSON text, or a {{reference}} to earlier output. A skeleton of the accepted fields is available through the drawer's "Show default". CRM single-record bodies use a properties object; batch bodies use an inputs array.
  • Forward All Headers, Additional Headers and Additional Query Parameters — escape hatches. Additional values override declared ones with the same name.

Working with properties

HubSpot records hold their data in a properties object, not top-level fields. When creating or updating, properties is a flat map of HubSpot property names to values, for example {"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 such as email,firstname,lastname,hs_lead_status.

Searching

The Search action on each CRM object takes a body with filterGroups (filters within one group are AND'd, groups are OR'd), sorts, properties, limit and after. Write it as JSON in the Request Body field:

json
{
  "filterGroups": [
    { "filters": [ { "propertyName": "email", "operator": "EQ", "value": "user@example.com" } ] }
  ],
  "properties": ["email", "firstname", "lastname"],
  "limit": 10
}

Reading the response

Results land at {{<stepReference>.response...}} (see Step Reference), in the same envelope as the HTTP Connector:

  • {{<stepReference>.response.status}}, {{<stepReference>.response.statusText}} and {{<stepReference>.response.headers.<header-name>}}.
  • {{<stepReference>.response.data.<field>}} — the parsed HubSpot body.

HubSpot's body shapes are consistent across the CRM object modules:

  • A single record (read / create / update / merge) — .response.data.id, .response.data.properties.<name>, .createdAt, .archived, and .associations when requested. A step named "Read" resolves a contact's email at {{read.response.data.properties.email}}.
  • A list — .response.data.results is the array and .response.data.paging.next.after is the cursor to pass back as after (absent on the last page).
  • A search — results, total (total matches) and paging.next.after.
  • A batch operation — results plus status COMPLETE (HTTP 200/201). On partial failure HubSpot returns HTTP 207 with an errors array, so check {{<stepReference>.response.status}} to tell them apart.
  • Archive / delete / GDPR-delete — 204 No Content with an empty body. Confirm success with the status.

Modules outside the CRM object pattern (Associations, Pipelines, Lists, Files, Meetings, Webhooks, Imports, Exports) return HubSpot's own payload for that endpoint; for example Owners returns id, email, firstName, lastName, userId, Pipelines and Search Lists return results / lists arrays, and Get Status of Export returns a time-limited download link at .response.data.result once .response.data.status is COMPLETE.

Pagination

List and Search return a page (set its size with limit). Pass {{<stepReference>.response.data.paging.next.after}} back as after to fetch the next page; when paging is absent you have the last page.

Error handling

A non-2xx HubSpot response is returned as a normal result with the real status code and HubSpot's error body at {{<stepReference>.response.data}} (typically .message and .category), so you can branch on {{<stepReference>.response.status}}. Common cases: 401 (bad or expired token), 403 (missing Private App scope), 404 (archived record or wrong id) and 429 (rate limited; HubSpot's Retry-After is at {{<stepReference>.response.headers.retry-after}}).

Common tasks

Find a contact and update it

  1. Run the Contacts Search action (named "Find Contact", reference findContact) with the email filter from Searching.
  2. Run the Contacts Update action with contactId {{findContact.response.data.results[0].id}} and body { "properties": { "lifecyclestage": "customer" } }.
  3. Confirm with {{updateContact.response.data.properties.lifecyclestage}}.

Attach a line item to a deal

  1. Run Line Items Create with { "properties": { "name": "Annual plan", "quantity": "1", "price": "1200" } } (reference createLineItem).
  2. Run Associations Associate records (labelled) with fromObjectType line_items, toObjectType deals and body { "inputs": [ { "from": { "id": "{{createLineItem.response.data.id}}" }, "to": { "id": "<dealId>" }, "types": [ { "associationCategory": "HUBSPOT_DEFINED", "associationTypeId": <typeId> } ] } ] }.

Move a deal to a new stage

  1. Run Pipelines Return all stages of a pipeline with objectType deals and the pipelineId, and find the stage's id in results.
  2. Run Deals Update with dealId and body { "properties": { "dealstage": "<stage id>" } }.

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}}:

text
{{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:

text
{{steps[0].response.data.id}}

Referencing trigger data

text
{{trigger.headers.authorization}}
{{trigger.body.customerId}}
{{trigger.query.page}}
{{trigger.params.orderId}}
{{trigger.method}}
{{trigger.path}}

Referencing workflow variables

text
{{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:

text
{{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.

Was this page helpful?

Last updated 9 October 2026