# Push to HubSpot

> Write sanda's numbers into properties on your HubSpot contacts, companies, deals, tickets and custom objects. Each run sends only what changed.

A push to HubSpot writes sanda's numbers into properties on your HubSpot records: lifetime revenue on each company, the open balance on each deal, the date of a customer's last job on their contact. The numbers come from your [semantic fluid](https://docs.sanda-os.com.au/semantic-fluid), so they mean what your team agreed they mean, and they land in ordinary HubSpot properties that lists, reports and workflows read like any other.

It works as every push does (see [Push](https://docs.sanda-os.com.au/streams/out)): each run reads the values, writes them the way HubSpot stores them, compares them with what HubSpot last accepted, and sends only the records whose values changed.

:::note
HubSpot is under **Choose a destination** on the **Push** tab once pushes to HubSpot are switched on for your console. If it isn't listed, ask sanda support.
:::

## What it writes

A push writes to one object of one HubSpot account, and finds each record one of two ways:

| Finds records by | What a run does |
|---|---|
| **HubSpot record ID** | Updates records that already exist, and never creates one. A row whose record HubSpot doesn't have is refused. |
| **A unique property** | Updates the record with that value, and creates a record when none has it. For contacts, the email address is one. |

| Object | Listed when |
|---|---|
| Contacts, companies and deals | Always |
| Tickets | The connection was given access to tickets |
| Your custom objects | The connection was given access to custom objects, which HubSpot has on its Enterprise subscriptions |

A push writes properties and nothing else. It never deletes a record, never adds or removes an association between records, and never clears a property because a record has left the push: that record keeps the value it has.

Companies, deals, tickets and custom objects have no unique property of their own that HubSpot enforces, so a push that should create them needs one you make. See [make a unique property](#make-a-unique-property).

## Connect HubSpot

### Before you start

- **Who connects installs sanda's app.** The person who connects HubSpot installs sanda's app into the HubSpot account, so they need to be a super admin there, or to have the Marketplace Access permission. The push writes with the app's access, not that person's.
- **Be an owner or admin in sanda.** Only owners and admins connect an account and make a push, because a push writes into HubSpot on the whole workspace's behalf.

### Connect an account

:::steps
1. **Open Push.** In the console, go to **Data · Streams** and open the **Push** tab.
2. **Start connecting.** Press the **HubSpot** card under **Choose a destination**. Once an account is connected, the card opens a new push instead: to connect another, press **Connect HubSpot** beside HubSpot under **Connected**.
3. **Sign in to HubSpot and choose the account.** HubSpot shows what sanda's app asks for: to write contacts, companies and deals and to read their properties, and, where your account has them, to write tickets and custom objects. Allow it.
4. **Check the account.** You land back in sanda with a line naming the account and who connected it, such as **Acme HubSpot is connected, as jo@acme.com.** From the card, you land in a new push with the account chosen. The account is listed under **Connected**, beside HubSpot.
:::

Connect each account once, however many pushes write to it.

### If Connect fails

You land back on **Push** with a sentence saying what happened, and nothing is connected.

| The page says | What to do |
|---|---|
| HubSpot asked whether to allow sanda and the answer was no | Press **Connect HubSpot** again and allow sanda's app. |
| sanda couldn't match HubSpot's answer to this tab | Connecting started in another tab or workspace, or was left too long before it finished. Press **Connect HubSpot** again from this tab. |
| HubSpot won't take another connection from sanda's app just now | HubSpot limits how many accounts an app can be installed in until HubSpot lists it on its Marketplace. Try again later, and if it happens again, sanda support can see why. |
| HubSpot refused the connection | HubSpot's sign-in turned the request down. Try again, and if it happens again, sanda support can see why. |
| Only owners and admins can connect a HubSpot account | Ask an owner or admin of the workspace. |

### Disconnect

Press **Disconnect** beside the account under **Connected**, then confirm. sanda forgets its token. To remove sanda's access in HubSpot too, a super admin uninstalls it under **Settings · Integrations · Connected apps** in HubSpot. The pushes that write to the account stay, and run again once you connect it. Nothing in HubSpot changes: the values pushes already wrote stay where they are.

## Create a push to HubSpot

Press **New push** on the **Push** tab, or the **HubSpot** card once an account is connected. The editor asks the same four things as any push (see [create a push](https://docs.sanda-os.com.au/streams/create-a-stream-out)), in HubSpot's words.

### Where it writes

- **Destination**, on a new push when more than one destination has an account connected: choose **HubSpot**.
- **HubSpot account**: the account the push writes to. An account that no longer allows sanda all a push needs is listed but can't be chosen, with **Connect again** under it.
- **Object**: the object whose records get the values, under **Standard objects** and **Custom objects**.

### How each record is found

- **HubSpot property**: **Record ID (HubSpot record ID)**, or a property marked **unique property**. The section's lead says what the choice means: by the record ID the push only updates records that already exist, and by a unique property a row with no matching record becomes a new one.
- **sanda value that holds it**: a column or a dimension, never a metric, because it says which one record a row is about.

sanda compares keys the way HubSpot matches them: an email address without its spaces and in lower case, a number written plainly, a record ID as its digits. Two rows whose keys differ only in capitals are one HubSpot record, so neither is sent, the same as any two rows that match one record. Group the push by the field that identifies the record so each has one row.

### What it writes

Under **What it writes**, pick a HubSpot property on the left and the value for it on the right. Every property can take a sanda value, or **A fixed value instead** for one that is the same on every record, such as a deal's pipeline. Press **Add a property** for each one more. A push writes up to 100 properties.

- The line under each property says what it takes: text, a number, a date, a date and time, a checkbox, one of its choices (any of them, for multiple checkboxes), or an ID. An owner property says it takes a HubSpot owner ID, a number.
- Properties HubSpot works out itself, or keeps read-only, are listed under **properties sanda can't write, and why**.
- A push that creates records needs what HubSpot needs to create one. For deals that is **Deal name** and **Deal stage**, and for tickets the ticket's name and its status; a custom object says its own. The editor names any the push doesn't write, and saving warns of them again. A new deal or ticket sent without its pipeline goes in the default pipeline, so map **Pipeline** too when the stage belongs to another one: the line under it says so.
- A contacts push found by email is warned that HubSpot takes such an upsert all or nothing for each request of 100 contacts, so one refused contact costs sanda extra requests to find. A unique property of your own avoids it.

Saving checks the mapping against the object as HubSpot has it now, and refuses in a sentence anything HubSpot would not take, such as a property it no longer has.

### Make a unique property

A push that should create companies, deals, tickets or custom object records finds them by a property HubSpot keeps unique, such as your job system's customer ID. Make it in HubSpot before you set up the push:

:::steps
1. **Open the object's properties.** In HubSpot, open **Settings**, then **Properties**, and choose the object.
2. **Create the property** as single-line text or a number.
3. **Require unique values.** On the property's **Rules** tab, turn on **Require unique values for this property**, then create it.
:::

HubSpot offers this only when a property is created, and allows only a few unique properties on each object. Once it exists, reopen the push's editor: the property is offered under **HubSpot property** as a **unique property**. A push writes the key into that property on every record it creates.

## How values are written

Every value is written the way HubSpot stores it, before it is compared with what HubSpot last accepted:

- **Numbers** are written plainly, never in scientific notation, so a value that only looked different (`100.0` and `100`) isn't a change.
- **Checkboxes** take yes or no, true or false, or one or zero.
- **Dates** take the value's day in your workspace's [reporting time zone](https://docs.sanda-os.com.au/workspace#reporting-time-zone), and **dates with times** the instant itself.
- **Choices** (a dropdown, radio buttons, a deal stage) match a choice's internal value, or a label that names exactly one choice, and HubSpot's own value is sent. A label two choices share, such as a stage named alike in two pipelines, is refused: use the choice's internal value instead.
- **Multiple checkboxes** take several choices separated by semicolons, the way HubSpot writes them, and replace what the record had. A comma is part of a choice's name, not a separator, so a choice such as `$1,000 - $5,000` matches whole.
- **An empty value clears the property**, so a metric with nothing to measure doesn't leave last month's number standing.

A value that doesn't fit its property is refused before anything is sent, and the run lists it.

:::caution Lifecycle stage
HubSpot only moves a lifecycle stage forward. A push that sends an earlier stage than a record has is ignored there, though sanda counts it as sent.
:::

## How a run sends

A run sends its changed records in requests of up to 100, and at most 10,000 records a run: a first run of a large push takes several runs to send everything, and what a run leaves is counted as **left for a later run**. A run also stops sending after 7 minutes, and the next run carries on. Each run's detail says how many HubSpot API calls it used.

sanda paces its requests to stay inside HubSpot's limit for an app, and when HubSpot asks it to slow down it waits and carries on. When HubSpot says the account's API calls for the day are used up, the run stops, records what went, and the next run carries on.

**Send everything again**, on the push, sends every record it reads again over the next runs, including the ones HubSpot already has, and releases held records. Use it when values in HubSpot were changed by hand. It costs about one API call for every 100 records. See [send everything again](https://docs.sanda-os.com.au/streams/runs#send-everything-again).

## Personal details and marketing contacts

A push to HubSpot sends values as they are, not hashed: HubSpot is your own CRM, and a contact's email address is how it finds the contact. sanda keeps each record's key (its record ID, email or unique value) and a fingerprint of the values HubSpot accepted, to tell what changed, never the values themselves. A refused record's line in a run keeps HubSpot's own message, which can quote the value it refused.

Contacts a push creates are non-marketing contacts unless your HubSpot account says otherwise. HubSpot lists sanda's app under **Settings · Integrations · Marketing Contacts**, and switching it on there makes the contacts it creates marketing contacts, which HubSpot bills for.

## When a run says why

### Records HubSpot refused

HubSpot's own message comes first, because it names the property and any validation rule of your account's own. sanda adds a hint for the common ones:

| Code | What it means |
|---|---|
| `VALIDATION_ERROR` | HubSpot refused a value. Its message names the property; a validation rule set up in your HubSpot account is the usual reason. |
| `INVALID_OPTION` | The value isn't one of the property's choices. If the choice was added in HubSpot after the push was saved, open the push and save it again. |
| `MISSING_REQUIRED_PROPERTY` | A property HubSpot requires isn't in the push. |
| `OBJECT_NOT_FOUND` | No record in HubSpot has this ID. |
| `CONFLICT` | Another HubSpot record already has this value in a unique property. |
| `HUBSPOT_UNCONFIRMED` | HubSpot answered without saying what it did with the record, even when sanda asked about it alone. It is sent again until it is held. |
| `BECCA_TOO_LARGE` | The record's values together are larger than one HubSpot request carries. |
| `BECCA_DUPLICATE` | More than one row matches this HubSpot record, counting keys that differ only in capitals. |

A request HubSpot refuses whole without saying which record was at fault is split until the record is found, and only that record is refused. For a custom object, the first such refusal in a run makes sanda ask HubSpot whether the object is still there, so a deleted one stops the run (below) rather than having its records refused. A record HubSpot refuses 3 runs in a row for the same value is held. See [held records](https://docs.sanda-os.com.au/streams/runs#held-records).

### A run that stops

A refusal about the push rather than one record stops the run in one sentence, and nothing is held for it:

| The run says | What to do |
|---|---|
| HubSpot's daily API limit for this account is used up | Nothing. The next run carries on from where this one stopped. |
| HubSpot is moving this account's data to another region | Nothing. HubSpot takes writes again once the move is done, and the next run carries on. |
| A property isn't one HubSpot can take any more | The property was deleted, renamed or made read-only in HubSpot. Open the push, change or remove it, and save. |
| HubSpot has no object of that name any more | The custom object was deleted. Open the push and choose another object. |
| HubSpot didn't give sanda the access this push needs, or write access to these records | Connect the account again and allow sanda's app what it asks for. |
| HubSpot no longer accepts sanda's connection | The app was uninstalled, or the connection was revoked. Connect the account again to carry on. |

:::links
- [Push](https://docs.sanda-os.com.au/streams/out): How every push works, and what counts as a change.
- [Create a push](https://docs.sanda-os.com.au/streams/create-a-stream-out): The editor, the preview and when a push runs.
- [Runs](https://docs.sanda-os.com.au/streams/runs): What each run did, and why a record was refused or held.
:::
