HubSpot
Server-side event delivery to HubSpot via the official @hubspot/api-client SDK. Sends custom behavioral events, upserts contacts via the CRM API, and supports optional batch mode with graceful shutdown.
HubSpot is a server destination in the walkerOS flow:
Receives events server-side from the collector, resolves contact identity (email or objectId), optionally upserts contact properties, then sends the custom event to HubSpot.
Installation
npm install @walkeros/server-destination-hubspot- Integrated
- Bundled
import { startFlow } from '@walkeros/collector';
import { destinationHubspot } from '@walkeros/server-destination-hubspot';
await startFlow({
destinations: {
hubspot: {
code: destinationHubspot,
config: {
settings: {
accessToken: 'YOUR_HUBSPOT_ACCESS_TOKEN',
eventNamePrefix: 'pe12345678_',
},
},
},
},
});Add to your flow.json destinations:
"destinations": {
"hubspot": {
"package": "@walkeros/server-destination-hubspot",
"config": {
"settings": {
"accessToken": "YOUR_HUBSPOT_ACCESS_TOKEN",
"eventNamePrefix": "pe12345678_"
}
}
}
}Configuration
This destination uses the standard destination config wrapper (consent, data, env, id, ...). For the shared fields see destination configuration. Package-specific fields live under config.settings and are listed below.
Settings
| Property | Type | Description | More |
|---|---|---|---|
accessToken | string | HubSpot private app access token. Create one in HubSpot Settings > Integrations > Private Apps. Requires analytics.behavioral_events.send scope. | |
eventNamePrefix | string | Fully qualified event name prefix: pe{HubID}_ (e.g. pe12345678_). Find it in HubSpot under Data Management > Custom Events. | |
email | string | walkerOS mapping value path to resolve contact email from events (like user.email). Required for contact association. | |
objectId | string | walkerOS mapping value path to resolve HubSpot CRM objectId from events. Alternative to email for contact association. | |
identify | any | Destination-level contact upsert mapping. Resolves to { email, properties }. Fires contact update on first push and re-fires when values change. | |
defaultProperties | Record<string, string> | Static event properties added to every event occurrence. Useful for hs_touchpoint_source, hs_page_content_type, etc. | |
batch | boolean | Use batch API for events (accumulate and flush). Default: false. | |
batchSize | integer | Batch size before auto-flush. Only used when batch: true. Default: 50. Max: 500. |
Mapping
Per-event rules under config.mapping. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.
| Property | Type | Description | More |
|---|---|---|---|
eventName | string | Override eventName for this rule. Without the prefix -- just the event name part (e.g. purchase_completed). The eventNamePrefix is prepended automatically. | |
identify | any | Per-event contact upsert. Resolves to { email, properties }. Overrides destination-level identify. Use with silent: true on login/identify events. | |
properties | any | Additional event properties mapping. Resolved values are merged with defaultProperties and serialized to strings. |
Examples
Default event
A walker event is sent to HubSpot as a custom behavioral event keyed by the user email.
{
"name": "product view",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"detail",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"email": "user@example.com"
},
"nested": [],
"consent": {
"functional": true
},
"id": "dabc249a080b5164",
"trigger": "load",
"entity": "product",
"action": "view",
"timestamp": 1700000100,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}events.send.basicApi.send({
"eventName": "pe12345678_product_view",
"email": "user@example.com",
"occurredAt": {},
"properties": {}
})Default properties
Destination-level default properties are merged into every HubSpot event payload, such as traffic source metadata.
{
"name": "page view",
"data": {
"domain": "www.example.com",
"title": "walkerOS documentation",
"referrer": "https://www.walkeros.io/",
"search": "?foo=bar",
"hash": "#hash",
"id": "/docs/"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"pagegroup": "docs"
},
"custom": {
"completely": "random"
},
"user": {
"email": "user@example.com"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "2c7cdc5e9b3583a6",
"trigger": "load",
"entity": "page",
"action": "view",
"timestamp": 1700000102,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}events.send.basicApi.send({
"eventName": "pe12345678_page_view",
"email": "user@example.com",
"occurredAt": {},
"properties": {
"hs_touchpoint_source": "walkerOS",
"hs_page_content_type": "STANDARD_PAGE"
}
})Destination identify
Destination-level identify upserts the HubSpot contact with mapped properties before sending the behavioral event.
{
"name": "page view",
"data": {
"domain": "www.example.com",
"title": "walkerOS documentation",
"referrer": "https://www.walkeros.io/",
"search": "?foo=bar",
"hash": "#hash",
"id": "/docs/"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"pagegroup": "docs"
},
"custom": {
"completely": "random"
},
"user": {
"email": "user@example.com",
"firstName": "Jane",
"lastName": "Doe"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "35fbcbe5e25046e0",
"trigger": "load",
"entity": "page",
"action": "view",
"timestamp": 1700000103,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}crm.contacts.basicApi.update("user@example.com", {
"properties": {
"firstname": "Jane",
"lastname": "Doe"
}
}, "email");
events.send.basicApi.send({
"eventName": "pe12345678_page_view",
"email": "user@example.com",
"occurredAt": {},
"properties": {}
})Custom event name
A mapping supplies a custom HubSpot event name and maps order data into properties for the behavioral event.
{
"name": "order complete",
"data": {
"total": 99.5,
"currency": "EUR",
"id": "ord-123"
},
"context": {
"shopping": [
"complete",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"email": "user@example.com"
},
"nested": [
{
"entity": "product",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "product",
"data": {
"id": "cc",
"name": "Cool Cap",
"size": "one size",
"price": 42
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
},
{
"entity": "gift",
"data": {
"name": "Surprise"
},
"context": {
"shopping": [
"complete",
0
]
},
"nested": []
}
],
"consent": {
"functional": true
},
"id": "11c68f2a51a95a15",
"trigger": "load",
"entity": "order",
"action": "complete",
"timestamp": 1700000101,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"name": "order complete",
"settings": {
"eventName": "purchase_completed",
"properties": {
"map": {
"revenue": "data.total",
"currency": "data.currency",
"order_id": "data.id"
}
}
}
}events.send.basicApi.send({
"eventName": "pe12345678_purchase_completed",
"email": "user@example.com",
"occurredAt": {},
"properties": {
"revenue": "99.5",
"currency": "EUR",
"order_id": "ord-123"
}
})Object id association
The HubSpot event is associated via objectId instead of email, resolved from the walker user id.
{
"name": "product view",
"data": {
"id": "ers",
"name": "Everyday Ruck Snack",
"color": "black",
"size": "l",
"price": 420
},
"context": {
"shopping": [
"detail",
0
]
},
"globals": {
"pagegroup": "shop"
},
"custom": {
"completely": "random"
},
"user": {
"id": "hs-contact-789"
},
"nested": [],
"consent": {
"functional": true
},
"id": "15550d2a2321085b",
"trigger": "load",
"entity": "product",
"action": "view",
"timestamp": 1700000105,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}events.send.basicApi.send({
"eventName": "pe12345678_product_view",
"objectId": "hs-contact-789",
"occurredAt": {},
"properties": {}
})User login identify
A user login only upserts the HubSpot contact with profile and lifecycle properties, skipping the event send.
{
"name": "user login",
"data": {
"email": "login@acme.com",
"first_name": "Jane",
"last_name": "Doe",
"lifecycle": "lead"
},
"context": {
"dev": [
"test",
1
]
},
"globals": {
"lang": "elb"
},
"custom": {
"completely": "random"
},
"user": {
"email": "user@example.com"
},
"nested": [
{
"entity": "child",
"data": {
"is": "subordinated"
}
}
],
"consent": {
"functional": true
},
"id": "0da32bf3e5207f59",
"trigger": "test",
"entity": "user",
"action": "login",
"timestamp": 1700000104,
"timing": 3.14,
"source": {
"count": 1,
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"type": "collector",
"schema": "4"
}
}{
"silent": true,
"settings": {
"identify": {
"map": {
"email": "data.email",
"properties": {
"map": {
"firstname": "data.first_name",
"lastname": "data.last_name",
"lifecyclestage": "data.lifecycle"
}
}
}
}
}
}crm.contacts.basicApi.update("login@acme.com", {
"properties": {
"firstname": "Jane",
"lastname": "Doe",
"lifecyclestage": "lead"
}
}, "email")Every event must be associated with a contact via email or objectId. The email path defaults to user.email. Events where neither value can be resolved are skipped with a warning.
Contact upsert
Use the identify setting (destination-level or per-rule) to upsert HubSpot contact properties alongside event tracking. The resolved mapping must produce { email, properties }. State-based dedup prevents redundant API calls when identity has not changed.