Skip to main content
Ask your AI

HubSpot

Server Source code Package Beta

Server-side event delivery to HubSpot via the official @hubspot/api-client SDK. Sends custom behavioral events, upserts contacts via the CRM API, and batches events through the collector's config.batch.

Where this fits

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
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_',
        },
      },
    },
  },
});

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​

PropertyTypeDescriptionMore
accessToken*stringHubSpot private app access token. Create one in HubSpot Settings > Integrations > Private Apps. Requires analytics.behavioral_events.send scope.
eventNamePrefix*stringFully qualified event name prefix: pe{HubID}_ (e.g. pe12345678_). Find it in HubSpot under Data Management > Custom Events.
emailstringwalkerOS mapping value path to resolve contact email from events (like user.email). Required for contact association.
objectIdstringwalkerOS mapping value path to resolve HubSpot CRM objectId from events. Alternative to email for contact association.
identifyanyDestination-level contact upsert mapping. Resolves to { email, properties }. Fires contact update on first push and re-fires when values change.
defaultPropertiesRecord<string, string>Static event properties added to every event occurrence. Useful for hs_touchpoint_source, hs_page_content_type, etc.
* Required fields

Mapping​

Per-event rules under config.mapping. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.

PropertyTypeDescriptionMore
eventNamestringOverride eventName for this rule. Without the prefix -- just the event name part (e.g. purchase_completed). The eventNamePrefix is prepended automatically.
identifyanyPer-event contact upsert. Resolves to { email, properties }. Overrides destination-level identify. Use with silent: true on login/identify events.
propertiesanyAdditional 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.

Event
{
  "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": "db22fb16c70f663d",
  "trigger": "load",
  "entity": "product",
  "action": "view",
  "timestamp": 1700000100,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Out
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.

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"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "f0cc5f6fec3d8eeb",
  "trigger": "load",
  "entity": "page",
  "action": "view",
  "timestamp": 1700000102,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Out
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.

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": "f0cc5f6fec3d8eeb",
  "trigger": "load",
  "entity": "page",
  "action": "view",
  "timestamp": 1700000103,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Out
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.

Event
{
  "name": "order complete",
  "data": {
    "value": 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": "235d0d7bd7a213f2",
  "trigger": "load",
  "entity": "order",
  "action": "complete",
  "timestamp": 1700000101,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "name": "order complete",
  "settings": {
    "eventName": "purchase_completed",
    "properties": {
      "map": {
        "revenue": "data.value",
        "currency": "data.currency",
        "order_id": "data.id"
      }
    }
  }
}
Out
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.

Event
{
  "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": "db22fb16c70f663d",
  "trigger": "load",
  "entity": "product",
  "action": "view",
  "timestamp": 1700000105,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Out
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.

Event
{
  "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": "d5b7b5f423841969",
  "trigger": "test",
  "entity": "user",
  "action": "login",
  "timestamp": 1700000104,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "collector",
    "schema": "4"
  }
}
Mapping
{
  "silent": true,
  "settings": {
    "identify": {
      "map": {
        "email": "data.email",
        "properties": {
          "map": {
            "firstname": "data.first_name",
            "lastname": "data.last_name",
            "lifecyclestage": "data.lifecycle"
          }
        }
      }
    }
  }
}
Out
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. An event where neither value can be resolved fails with the missing-input code, and a failed HubSpot call fails the push with HubSpot's HTTP status. A 429 and a 5xx answer are retryable.

Batching​

Set config.batch to send events through HubSpot's batch API instead of one request per event. The options are in Batch scheduling. A flush sends chunks of 500 events, and each event keeps its own outcome. settings.batch and settings.batchSize no longer exist; a flow that sets them fails walkeros validate.

"config": {
  "batch": { "size": 100, "wait": 5000 },
  "settings": { "accessToken": "YOUR_HUBSPOT_ACCESS_TOKEN" }
}

A batched event answers queued with the reason batch. A failed flush is reported (one ERROR line, the failed counter, the dead-letter queue), but the producer's message is not redelivered.

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.

💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)