# HubSpot Cloud Mode Integration (New API v3)


RudderStack supports the following API calls while sending data to Hubspot via [cloud mode]({{< ref "destinations/rudderstack-connection-modes.md#cloud-mode" >}}) using the [new API v3](https://developers.hubspot.com/docs/api/overview).

{{< warning >}}
RudderStack does not support the [new datetime data type](https://www.youtube.com/watch?v=2YjCUQUt8iY&ab_channel=HubSpot) introduced by HubSpot.
{{< /warning >}}

## Identify

RudderStack sends the [`identify`]({{< ref "event-spec/standard-events/identify.md" >}}) call to check if a contact exists, if it does, RudderStack updates the contact with new information. Otherwise, it creates a new contact.

A sample `identify` call is as shown:

```javascript
rudderanalytics.identify({
  firstName: "Alex",
  city: "New Orleans",
  country: "USA",
  phone: "+1-202-555-0146",
  email: "alex@example.com",
  custom_flavor: "chocolate",
  custom_date: 1574769933368,
  custom_date1: new Date("2019-10-14T11:15:53.296Z"),
})
```

Make sure to specify the HubSpot property you set in **HubSpot property to be used for upsert** [dashboard setting]({{< ref "destinations/streaming-destinations/hubspot/setup-guide.md#new-api-settings" >}}) in the `traits` object of the `identify` call — RudderStack sends this property to HubSpot for matching contacts.

{{< tip >}}
For best performance, choose a HubSpot contact property that is unique in your account. Unique properties let RudderStack call HubSpot's [batch upsert contacts API](https://developers.hubspot.com/docs/api-reference/crm-contacts-v3/batch/post-crm-v3-objects-contacts-batch-upsert) for `identify` events, which significantly increases throughput. 

Non-unique properties will still work, but RudderStack uses a slower search-based flow.
{{< /tip >}}

```javascript
rudderanalytics.identify({
  name: "Alex Keener",
  phone: "+1-202-555-0146",
  uniqueId: "user-12345"
})
```

{{< info >}}
If you configure **HubSpot property to be used for upsert** in the dashboard but omit that property in the `identify` traits, RudderStack uses `email` as the default field for matching.
{{< /info >}}

### Trait mappings

The following table lists the mappings between the RudderStack properties and the HubSpot properties:

| RudderStack property | HubSpot property |
| :-----| :-----|
| `traits.email` <br /> `context.traits.email` <br /> `properties.email` | `email` |
| `traits.firstName` <br /> `traits.firstname` <br /> `traits.first_name` <br /> `context.traits.firstName` <br /> `context.traits.firstname` <br /> `context.traits.first_name` <br /> `properties.firstname` | `firstname` |
| `traits.lastName` <br /> `traits.lastname` <br /> `traits.last_name` <br /> `context.traits.lastName` <br /> `context.traits.lastname` <br /> `context.traits.last_name` <br /> `properties.lastname` | `lastname` |
| `phone` | `phone` |
| `traits.address.street` <br /> `context.traits.address.street` <br /> `properties.address.street` | `address` |
| `traits.address.city` <br /> `context.traits.address.city` <br /> `properties.address.city` | `city` |
| `traits.address.country` <br /> `context.traits.address.country` <br /> `properties.address.country` | `country` |
| `traits.address.state` <br /> `context.traits.address.state` <br /> `properties.address.state` | `state` |
| `traits.address.postalcode` <br /> `context.traits.address.postalcode` <br /> `properties.address.postalcode` | `zip` |
| `traits.company.name` <br /> `context.traits.company.name` <br /> `properties.company.name` | `company` |

### Handle null and empty property values

The following table explains how specific trait values are handled when you update HubSpot properties from Event Stream or a [Reverse ETL]({{< ref "sources/reverse-etl/" >}}) source connected to this destination:

| Trait value | Event Stream | Reverse ETL |
| :--- | :--- | :--- |
| `null` | Clears the property | Leaves the property unchanged |
| `""` (empty string) | Clears the property | Clears the property |
| `undefined` | Leaves the property unchanged | Leaves the property unchanged |
| `false` or `0` | Sent unchanged | Sent unchanged |

- In an **Event Stream** connection, `null` trait values are converted to `""` before the request is sent. HubSpot treats `""` as a clear signal and removes the property value.
- In a **Reverse ETL** connection, `null` values are omitted from the update payload, so the existing HubSpot value is not modified.

{{< tip >}}
To clear a HubSpot property from a Reverse ETL sync, map an empty string (`""`) for that field.
{{< /tip >}}

The HubSpot API clears properties only when you send `""` — it does not accept `null`. 

See the following HubSpot references for more information:

- [CRM Properties API guide](https://developers.hubspot.com/docs/api-reference/crm-properties-v3/guide)
- [Clear a property value](https://developers.hubspot.com/docs/api-reference/legacy/crm/properties/guide#clear-a-property-value)

## Track

A [`track`]({{< ref "event-spec/standard-events/track.md" >}}) call lets you record various user actions and any properties associated with them.

To associate a `track` call with a user, you need to specify the user's `email` under `context.traits`. 

{{< info >}}
RudderStack associates the `track` events with the same user only after you make a successful `identify` call. However, if you send a `track` call without making any `identify` call first and there is no contact present in HubSpot, RudderStack will not associate the events as there is no identifier.
{{< /info >}}

A sample `track` event is as shown:

```javascript
rudderanalytics.track(
  "Order Completed", {
    value: 30,
  }, {
    context: {
      traits: {
        firstname: "Alex",
        city: "New Orleans",
        country: "USA",
        phone: "+1-202-555-0146",
        email: "alex@example.com",
      },
    },
  }
)
```

### Property mappings

The following table lists the properties and their mappings between RudderStack and HubSpot for the `track` call:

| RudderStack property         | HubSpot property      | 
| :--------------------------- | :--------------------- |
| `traits.utk` <br /> `context.traits.utk` <br /> `properties.utk`  | `utk`     | 
| `traits.email` <br />  `context.traits.email`        | `email`       |   
| `traits.objectId` <br /> `context.traits.objectId` <br /> `properties.objectId`    | `objectId`     |            
| `properties.occurred_at` <br /> `timestamp` <br /> `originalTimestamp`  | `occurredAt`       |      

{{< warning >}}
You must send either of the `utk`, `email`, or `objectId` (can be `contact_id` or `visitor_ id`) properties to make a `track` call successfully.
{{< /warning >}}

### Custom behavioral events

{{< warning >}}
The custom behavorial events can be used for both the authentication types. However, they must have the `analytics.behavioral_events.send` permission to be used for private apps.
{{< /warning >}}

[Custom behavioral events](https://developers.hubspot.com/docs/api/analytics/events) are account-defined events in HubSpot that store event details in the event properties. You can create custom behavioral events and their associated properties in the RudderStack dashboard as explained in the [New API]({{< ref "destinations/streaming-destinations/hubspot/setup-guide.md#new-api" >}}) section.

The following parameters are sent in the custom behavorial events:

- **Identifier**: Either the contact ID, email, or [utk](https://developers.hubspot.com/docs/api/events/tracking-code) (user token) of the contact associated with the event. The utk is the user token stored in the visitor's `hubspotutk` browser cookie.
- **Event name:** The internal name of the event which can be found in HubSpot.
- **Properties object**: When you create a custom behavioral event in HubSpot, some default properties are provided with those events, explained in the below section.

#### Behavioral events property mappings

The following table lists the **optional** and default property mappings between RudderStack and HubSpot for custom behavioral events:

| RudderStack property         | HubSpot property      | 
| :--------------------------- | :--------------------- |
| `properties.assetDescription` <br /> `properties.hsAssetDescription`  | `hs_asset_description` | 
| `properties.assetType` <br /> `properties.hsAssetType` | `hs_asset_type`       
| `properties.campaignId` | `hs_campaign_id`       |         
| `traits.address.city` <br /> `context.traits.address.city` <br /> `properties.address.city` | `hs_city` |          
| `traits.address.country` <br /> `context.traits.address.country` <br /> `properties.address.country` | `hs_country` |            
| `context.device.name` | `hs_device_name` |          
| `properties.elementClass` <br /> `properties.hsElementClass` | `hs_element_class` |             
| `properties.elementId` <br /> `properties.hsElementId` | `hs_element_id` | 

<br />
