Skip to main content

HubSpot CRM ExecModule

Overview

HubSpotCrmModule connects ValkyrAI workflows to HubSpot CRM v3 for contacts, companies, and deals. It creates and updates one record at a time, retrieves one record, or returns one bounded list or search page.

Version 2.0 replaces misleading “feature-complete” claims, empty input/output metadata, unreachable actions, inert tests, and unbounded provider access. The module now has an explicit 15-operation contract, a READY IntegrationAccount boundary, strict record/property/search validation, bounded request and response bodies, 10-second connection and 30-second read timeouts, verified response identities, normalized confidential outputs, and safe errors that never retain provider bodies or credentials.

Read-only get, list, and search calls retry HTTP 429 and transient 5xx responses at most twice after the first attempt. Create and update operations never retry automatically because HubSpot may have committed a write before a timeout or ambiguous provider response.

Usage

  1. Create a HubSpot private app with only the contact, company, and deal read/write scopes required by the selected operations.
  2. Store its access token only in the encrypted apiKey field of a ValkyrAI IntegrationAccount and set the account to READY.
  3. Bind that record through ExecModuleConfig.authConfig.integrationAccount.
  4. Select an operation and supply only its required fields.
  5. For list or search operations, consume the returned after cursor in a later workflow execution when hasMore is true.
  6. Treat all returned CRM properties as confidential or restricted workflow data.

Inputs

NameTypeRequiredDescriptionLocal constraints
operationstringYesHubSpot operation.One of the 15 operations listed below.
record_idstringConditionalRecord ID for get_* and update_*.1–256 safe identifier characters; path separators and controls are rejected.
propertiesobjectConditionalProperty map for create_* and update_*.Non-empty; at most 200 safe property names; scalar values only; serialized request at most 512 KB.
filter_groupsarrayConditionalHubSpot search filters.A search requires query or filter_groups; at most five groups, six filters per group, and 18 filters total.
querystringConditionalHubSpot text-search query.At most 3,000 Unicode code points and no control characters.
sortsarray of stringsNoSearch sort expressions.At most five bounded strings.
property_namesarray of stringsNoProperties to include in provider responses.At most 100 unique safe HubSpot property names.
afterstringNoOpaque HubSpot pagination cursor.At most 1,024 characters and no controls; never interpret or modify it.
limitintegerNoProvider page size.Defaults to 100; list calls allow 1–100 and search calls allow 1–200.

Create and update property values must be scalar JSON values. Nested objects and arrays are rejected so the connector cannot silently create an unsupported HubSpot property shape. create_company additionally requires properties.name or properties.domain; create_deal requires properties.dealname and properties.dealstage.

Outputs

NameTypeWhen presentDescription
hubspot.statusstringAlwaysSUCCESS after verified normalization or ERROR on failure.
hubspot.operationstringSuccessNormalized operation name.
hubspot.objectTypestringSuccesscontacts, companies, or deals.
hubspot.objectIdstringSingle-record successVerified provider record ID.
hubspot.dataobjectSingle-record successThe bounded HubSpot record response.
hubspot.itemsarrayList or search successOne bounded provider page of records.
hubspot.countintegerList or search successNumber of items in this page.
hubspot.hasMorebooleanList or search successWhether HubSpot supplied a next cursor.
hubspot.afterstringWhen another page existsOpaque next-page cursor.

The runtime ExecModule is also marked GOOD or ERROR. CRM payloads are written to WorkflowState without being copied into progress or error logs. EventLogs contain only the operation, attempt count, and sanitized failure class.

IntegrationAccount Requirements

SettingRequirement
ProviderHubSpot CRM API v3
AuthenticationHubSpot private-app access token
StatusExactly READY
accountNameHuman-readable HubSpot portal/private-app identity
apiKeyEncrypted private-app token, 20–2,048 characters, with no whitespace or controls
RelationshipBind through ExecModuleConfig.authConfig.integrationAccount

Grant only the provider scopes needed by the chosen object types and actions, such as contact, company, or deal read/write scopes. Never place the token in payload fields, URLs, logs, examples, documentation, or workflow state.

Configuration

Illustrative normalized configuration:

{
"version": "2.0.0",
"authConfig": {
"authStrategy": 1,
"integrationAccount": "integration-account:hubspot-sales-private-app"
},
"payloadConfig": {
"parameters": "{\"operation\":\"search_contacts\",\"limit\":50,\"filter_groups\":[{\"filters\":[{\"propertyName\":\"lifecyclestage\",\"operator\":\"EQ\",\"value\":\"lead\"}]}]}"
}
}

The account value is a symbolic secure relationship. Persisted workflows use the generated IntegrationAccount reference, never a plaintext token.

Operations

ObjectCreateGetListSearchUpdate
Contactscreate_contactget_contactlist_contactssearch_contactsupdate_contact
Companiescreate_companyget_companylist_companiessearch_companiesupdate_company
Dealscreate_dealget_deallist_dealssearch_dealsupdate_deal

Provider requests use the corresponding /crm/v3/objects/{contacts|companies|deals} endpoint. Create uses POST; get and list use GET; search uses POST .../search; update uses PATCH .../{record_id}.

List creation, dynamic/static segment management, list membership mutation, record deletion, batch mutation, object association, owners, pipelines, custom-object discovery, and property-schema administration are not implemented. Version 2.0 deliberately removes those previously advertised but unreachable behaviors from this module’s contract.

Errors and Failure Modes

FailureCauseRetry guidance
Validation failureMissing/unsupported operation, unsafe ID, invalid property map, missing company/deal fields, malformed filters, or a local size/page limit.Correct the configuration; no request was sent.
IntegrationAccount failureNo bound account, non-READY status, or malformed private-app token.Repair the secure account; no request was sent.
HTTP 400/404Provider validation failure or missing record.Correct the record, property names, filter, pipeline, or stage before retrying.
HTTP 401/403Invalid token or missing private-app scope.Reauthorize the IntegrationAccount with the minimum required scopes.
HTTP 429HubSpot rate limit.Read calls use bounded retry; after exhaustion, honor the provider retry window. Writes are never retried automatically.
HTTP 5xx or network failureProvider or transport failure.Read calls use bounded retry. Reconcile create/update state in HubSpot before rerunning an ambiguous write.
Invalid success responseEmpty/oversized/invalid JSON, missing result array, unsafe record ID, or get/update ID mismatch.Treat as failure and investigate provider/API compatibility; success is not recorded.

Provider response bodies can contain CRM properties and credential-like diagnostic text. Failures therefore expose only a sanitized status, attempt count, and retry boundary. The original provider exception is not retained as the emitted error cause.

Example

Search for contacts in the lead lifecycle stage:

{
"operation": "search_contacts",
"filter_groups": [
{
"filters": [
{
"propertyName": "lifecyclestage",
"operator": "EQ",
"value": "lead"
}
]
}
],
"property_names": ["email", "firstname", "lastname", "lifecyclestage"],
"limit": 50
}

Expected normalized result when one page contains one record and no continuation:

{
"hubspot.status": "SUCCESS",
"hubspot.operation": "search_contacts",
"hubspot.objectType": "contacts",
"hubspot.items": [
{
"id": "12345",
"properties": {
"email": "lead@example.com",
"lifecyclestage": "lead"
}
}
],
"hubspot.count": 1,
"hubspot.hasMore": false
}

Notes

  • Pagination: each execution returns one page only. Pass the exact hubspot.after value into a later execution; the module does not paginate silently.
  • Limits: list pages cap at 100 and search pages at 200. Search payloads cap at 3,000 UTF-8 bytes, object requests at 512 KB, and provider responses at 5 MB.
  • Rate limits: read-only transient HTTP failures are retried up to three total attempts with a bounded delay. Provider Retry-After seconds are honored up to five seconds; HTTP-date values fall back to a bounded local delay.
  • Idempotency: reads are safe to retry. Create operations are not idempotent, and update success can be ambiguous after a transport failure. The module never treats WorkflowState as provider deduplication.
  • API constraints: HubSpot search indexing can lag newly created or updated records. Filters and property names must use HubSpot internal property identifiers.
  • Destructive behavior: record deletion and list membership mutation are not supported. Create/update still change external CRM state and can trigger HubSpot automation.
  • Confidentiality: returned contact, company, and deal properties are classified restricted; downstream steps must preserve tenant RBAC/ACL and data-minimization rules.
  • Observability: progress logs contain operation names and attempt counts, not tokens, property values, CRM responses, or provider error bodies.
  • Unverified boundary: deterministic tests cover request construction, validation, exact account readiness, read retry, pagination normalization, response identity, metadata serialization, no write retry, and token redaction. Live HubSpot execution requires separately authorized provider credentials and is not exercised by repository tests.

See HubSpot’s official CRM contacts guide, companies guide, deals guide, and CRM search limits for provider behavior.