# HubSpot webhook trigger

> Run a workflow on HubSpot CRM events: new contacts and companies, and watched property changes, with a shared org endpoint and v3 signature verification.

Source: https://cotera.co/docs/reference/webhooks/hubspot

---

Run a workflow on CRM events: a contact or company is created, or a property you watch changes. Each notification becomes the workflow's input — enrich the new contact, sync the company, react to a lifecycle-stage change.

For behavior shared by every provider (testing, deduplication), see the [webhook triggers reference](https://cotera.co/docs/reference/webhooks.md).

## Prerequisites

Setting up a HubSpot trigger requires a **connected HubSpot account** in Cotera. Without one, install fails with a credentials error.

HubSpot only exposes webhooks through a **legacy app**, so you'll need one to point at Cotera. Create it under **Settings → Integrations → Legacy Apps → Create Legacy App**. The legacy app is also where you grant the CRM scopes the subscriptions need — a `contact.*` subscription requires contact read access, `company.*` requires company read access.

## Setup

HubSpot allows only one webhook delivery URL per app, so Cotera doesn't create subscriptions over the API — you register the URL once on your legacy app:

1. Add a **From an app** trigger and pick **HubSpot**. Cotera gives you a single delivery URL shared by all your org's HubSpot triggers.
2. In HubSpot, create a legacy app under **Settings → Integrations → Legacy Apps → Create Legacy App** (or open an existing one), open its **Webhooks** settings, and set the target URL to Cotera's URL.
3. Grant the app the CRM scopes for the objects you're subscribing to (contacts, companies), then subscribe to the event types you want (contact creation, company property change, and so on).
4. Back in Cotera, add a trigger per event you care about — for property changes, set the property to watch.

All of your org's HubSpot events arrive on that one shared endpoint; Cotera routes each notification to the triggers that match it.

## Events

| Event key                  | Fires when                              | Config                       |
| -------------------------- | --------------------------------------- | ---------------------------- |
| `contact.created`          | A new contact is created                | Optional property conditions |
| `company.created`          | A new company is created                | Optional property conditions |
| `contact.property_changed` | A watched property changes on a contact | `propertyName` (required)    |
| `company.property_changed` | A watched property changes on a company | `propertyName` (required)    |

Property-change subscriptions are scoped to one property. Because every property change lands on the shared endpoint, Cotera gates each trigger to its configured `propertyName` (for example `lifecyclestage`) — so a contact-property trigger fires only for the property it watches.

## Payload

HubSpot delivers notifications in a batch array; Cotera splits the batch and runs your workflow once per matching notification, with that single notification as the input:

```json
{
  "objectId": 123456,
  "propertyName": "lifecyclestage",
  "propertyValue": "customer",
  "changeSource": "CRM_UI",
  "eventId": 3816279482,
  "subscriptionId": 61,
  "portalId": 33,
  "appId": 1160452,
  "occurredAt": 1462216307947,
  "eventType": "contact.propertyChange",
  "subscriptionType": "contact.propertyChange",
  "attemptNumber": 0
}
```

| Field                            | Description                                                                |
| -------------------------------- | -------------------------------------------------------------------------- |
| `objectId`                       | The CRM object (contact or company) the event is about.                    |
| `propertyName` / `propertyValue` | The changed property and its new value (property-change events only).      |
| `subscriptionType`               | HubSpot's event type, e.g. `contact.creation` or `company.propertyChange`. |
| `portalId`                       | The HubSpot account (portal) the event came from.                          |
| `occurredAt`                     | When the event happened, as a Unix-millisecond timestamp.                  |
| `eventId`                        | HubSpot's unique event id, used to deduplicate retries.                    |

Creation events (`contact.created`, `company.created`) carry the same shape without the `propertyName` / `propertyValue` fields.

## Signature verification

HubSpot signs each delivery with its **v3** scheme: a base64 HMAC-SHA256 over the request method, URL, body, and timestamp, sent in the `X-HubSpot-Signature-v3` header alongside an `X-HubSpot-Request-Timestamp`. Cotera verifies it — including a timestamp-freshness check to reject replays — using the HubSpot app's client secret, so there's no per-trigger secret for you to manage. Deliveries that fail verification are rejected before your workflow runs.

