IntegrationsCRMHubSpot

HubSpot

Creates or updates HubSpot contacts server-side through the CRM Contacts batch upsert API, keyed by email.

Why route HubSpot through Datafly Signal

  • Profiles are updated from the server, not the browser. Events reach HubSpot even when a tracker blocker or a failed page unload would have dropped a client-side call, so the profile reflects what actually happened.
  • Delivery is batched, rate-limited, and retried. Signal buffers to Kafka, respects the HubSpot rate limit, and retries on 429 or transient failure rather than dropping the event.
  • Presets for B2B / SaaS and Retail. The mapping starts from a working configuration for your vertical instead of a blank field map.
  • One consent decision governs every destination. HubSpot is gated by the same consent state as the rest of your stack, so a withdrawal applies everywhere at once.

Prerequisites

Create an access token

In HubSpot, create a Service Key (Development > Keys > Service Keys) or a private app, and grant the crm.objects.contacts.write scope. HubSpot stops creating legacy private apps on 28 September 2026 for new accounts and 26 October 2026 for existing accounts; tokens from existing legacy private apps keep working until September 2027. Copy the token.

Create any custom contact properties first

The upsert rejects the whole request when it names a property the portal does not have. The presets write only email, company and website, which every portal has. To write more, create the contact property in HubSpot (Settings > Properties), then add a mapping to it in the blueprint.

Configuration

FieldTypeRequiredDescription
access_tokensecretYesA HubSpot Service Key (Development > Keys > Service Keys) or a private app access token, with the crm.objects.contacts.write scope. Sent as a Bearer token. HubSpot stops creating legacy private apps on 28 September 2026 (new accounts) and 26 October 2026 (existing accounts); existing legacy tokens keep working until September 2027.
portal_idstringNoYour HubSpot Portal ID (Hub ID), shown in the account menu. Not used by the contacts upsert; kept for reference.

Signal Setup

  1. Navigate to Integrations in the sidebar and open the Integration Library tab.
  2. Find HubSpot, click Install and fill in the fields above.
  3. Pick the preset for your vertical, then review the mappings in the blueprint editor.
curl -X POST http://localhost:8084/v1/admin/integration-catalog/hubspot/install \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HubSpot",
    "config": {
      "access_token": "pat-eu1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    },
    "delivery_mode": "server_side"
  }'

API endpoint

POST https://api.hubapi.com/crm/v3/objects/contacts/batch/upsert
Authorization: Bearer {access_token}

Example body:

{"inputs": [{"id": "jane.doe@example.com", "idProperty": "email", "objectWriteTraceId": "evt-123",
  "properties": {"email": "jane.doe@example.com", "company": "Acme Ltd", "website": "https://acme.example"}}]}

The presets turn on the strict output contract: only the fields the blueprint maps are sent.

Event mapping

B2B / SaaS preset (saas)

Create or update the contact on identify, sign-up, lead, demo request, trial start and subscription start. Each event creates or updates the HubSpot contact keyed by email (sent in plain text: HubSpot’s upsert needs the address as the record key). Only standard contact properties are written (email, company, website); HubSpot rejects a batch that names a property the portal does not have, so add custom properties only after creating them in HubSpot. Event detail belongs in HubSpot custom events.

Signal eventSent to the vendor as
identifycontact_upsert
sign_upcontact_upsert
generate_leadcontact_upsert
demo_requestedcontact_upsert
trial_startedcontact_upsert
subscription_startedcontact_upsert

Retail preset (ecommerce)

Create or update the contact on identify, sign-up, lead and purchase. Each event creates or updates the HubSpot contact keyed by email (sent in plain text: HubSpot’s upsert needs the address as the record key). Only standard contact properties are written (email, company, website); HubSpot rejects a batch that names a property the portal does not have, so add custom properties only after creating them in HubSpot. Event detail belongs in HubSpot custom events.

Signal eventSent to the vendor as
identifycontact_upsert
sign_upcontact_upsert
generate_leadcontact_upsert
purchasecontact_upsert

Default preset (default)

Create or update the contact on identify, sign-up, lead, demo request and purchase. Each event creates or updates the HubSpot contact keyed by email (sent in plain text: HubSpot’s upsert needs the address as the record key). Only standard contact properties are written (email, company, website); HubSpot rejects a batch that names a property the portal does not have, so add custom properties only after creating them in HubSpot. Event detail belongs in HubSpot custom events.

Signal eventSent to the vendor as
identifycontact_upsert
sign_upcontact_upsert
generate_leadcontact_upsert
demo_requestedcontact_upsert
purchasecontact_upsert

Identity and personal data

The contact is keyed by email (idProperty: email), taken from the identity traits ($traits.email) or, on an identify() call, its own properties, then trimmed and lowercased. The email is sent in plain text: HubSpot keys the contact record on it. An event with no email is not sent. objectWriteTraceId carries the Signal event ID for tracing.

HubSpot events should typically be sent under the marketing consent category. HubSpot maintains its own legal basis and subscription opt-in records — Signal forwards CMP consent state on every event so workflows can keep these in sync.

Known limitations

  • HubSpot moved to date-based API versions on 30 March 2026 (/crm/objects/2026-03/contacts/batch/upsert, same body) and ends support for /crm/v3/ in September 2027. The integration still calls v3; the move is tracked in #1391.
  • Event detail (plan, order value) is not written to the contact: it needs custom properties, or HubSpot custom events (a separate integration).

Troubleshooting

SymptomCause and fix
400 with PROPERTY_DOESNT_EXISTA mapped property is missing from the contact schema. Create it in HubSpot or remove the mapping.
401 / 403Token revoked, or missing crm.objects.contacts.write.
Nothing sent for anonymous visitorsExpected: HubSpot needs an email to key the contact.

See also