> ## Documentation Index
> Fetch the complete documentation index at: https://docs.datalyr.com/llms.txt
> Use this file to discover all available pages before exploring further.

# HubSpot

> Send HubSpot contacts, qualified deals, and won deals to DATALYR through Zapier or a HubSpot workflow.

When you finish, each new HubSpot contact, qualified deal, and won deal arrives in **Events**, matched by email to the visit that started it.

HubSpot has no direct connection. You send each stage to the DATALYR ingest endpoint from Zapier or from a HubSpot workflow.

## What HubSpot sends

| HubSpot trigger | DATALYR event | `event_id` | Money role |
| - | - | - | - |
| New contact | `lead_submitted` | `hubspot_<contact ID>_lead_submitted` | None |
| Deal moves to your qualified stage | `lead_qualified` | `hubspot_<deal ID>_lead_qualified` | None |
| Deal moves to **Closed won** | `deal_won` | `hubspot_<deal ID>_deal_won` | Contract value. Never revenue |

Only `deal_won` carries a value: the deal `amount`. Its `cash_role` is `non_cash_fact`, so no revenue report counts it. Cash comes from a payment source such as [Stripe](/revenue/stripe) or [Commas](/integrations/commas).

Each `event_id` is stable per record and stage. A retried request is a duplicate, not a second conversion. See [Duplicates](/api-reference/ingest#duplicates).

## Before you start

| Requirement | Detail |
| - | - |
| DATALYR access | Owner or admin, to create the write key |
| Write key | The `Write key` from **Settings → API**. See [API keys](/account/api-keys) |
| Zapier route | A Zapier plan with multi-step Zaps and Webhooks by Zapier |
| HubSpot workflow route | HubSpot Data Hub Professional or above, for the **Send a webhook** action |
| Web tracking | The [tracking script](/getting-started/install-web-tracking) on your landing pages |

## The request

| Item | Value |
| - | - |
| Method | `POST` |
| URL | `https://ingest.datalyr.com/track` |
| Header | `X-API-Key: dk_YOUR_WRITE_KEY` |
| Header | `Content-Type: application/json` |

Replace each placeholder such as `{{email}}` with the matching HubSpot field. Keep `currency` and `cash_role` as written.

<CodeGroup>
  ```json lead_submitted theme={null}
  {
    "event": "lead_submitted",
    "user_id": "{{email}}",
    "event_id": "hubspot_{{contact_id}}_lead_submitted",
    "properties": {
      "email": "{{email}}",
      "phone": "{{phone}}",
      "lead_source": "hubspot",
      "hubspot_contact_id": "{{contact_id}}"
    }
  }
  ```

  ```json lead_qualified theme={null}
  {
    "event": "lead_qualified",
    "user_id": "{{email}}",
    "event_id": "hubspot_{{deal_id}}_lead_qualified",
    "properties": {
      "email": "{{email}}",
      "deal_id": "hubspot_{{deal_id}}",
      "lead_source": "hubspot"
    }
  }
  ```

  ```json deal_won theme={null}
  {
    "event": "deal_won",
    "user_id": "{{email}}",
    "event_id": "hubspot_{{deal_id}}_deal_won",
    "timestamp": "{{close_date}}",
    "properties": {
      "email": "{{email}}",
      "value": "{{amount}}",
      "currency": "USD",
      "deal_id": "hubspot_{{deal_id}}",
      "lead_source": "hubspot",
      "cash_role": "non_cash_fact"
    }
  }
  ```
</CodeGroup>

The **HubSpot** card in **Sources** has the same bodies.

<Warning>
  Keep the write key in Zapier or HubSpot only. Anyone holding it can write events into your workspace.
</Warning>

## Send stages from Zapier

Build one Zap per stage.

<Steps>
  <Step title="Add the HubSpot trigger">
    For `lead_submitted`, use **New Contact**. For the deal stages, use **Updated Deal Stage** and pick the stage.
  </Step>

  <Step title="Find the contact">
    For a deal stage, add **Find Associations** for the deal. Then add **Find Contact** to read the contact's email.
  </Step>

  <Step title="Add the request">
    Add **Webhooks by Zapier** with the **Custom Request** event. Set **Method** to `POST`.
  </Step>

  <Step title="Set the URL and headers">
    Paste the URL. Add the `X-API-Key` and `Content-Type` headers.
  </Step>

  <Step title="Paste the body">
    Paste the stage's body into **Data**. Map each placeholder to the field from an earlier step.
  </Step>

  <Step title="Test the step">
    Run the step test. Confirm the response contains `"success": true`.
  </Step>
</Steps>

## Send stages from a HubSpot workflow

<Steps>
  <Step title="Create the workflow">
    Create a contact-based workflow for `lead_submitted`, or a deal-based workflow for the deal stages.
  </Step>

  <Step title="Set the trigger">
    For deals, trigger on **Deal stage** equal to your qualified stage or **Closed won**.
  </Step>

  <Step title="Add Send a webhook">
    Add the **Send a webhook** action. Set the method to `POST` and paste the URL.
  </Step>

  <Step title="Authenticate">
    Add the write key as an API key sent in the `X-API-Key` header.
  </Step>

  <Step title="Build the body">
    Build the request body to match the stage's JSON above.
  </Step>
</Steps>

A deal-based workflow must send the associated contact's email as `user_id`. If it cannot, use Zapier.

## How deals match to ad clicks

HubSpot events match by email alone. `user_id` and `properties.email` carry it.

A HubSpot event joins an ad click only when DATALYR already knows that email from a tracked visit. That happens through an `identify()` call on your form, an [iClosed](/integrations/iclosed) or [Calendly](/integrations/calendly) booking, or a payment source. See [Identity](/advanced/identity).

## Set up conversion rules

HubSpot has no starter rules. Create each rule in **Conversions** with **manage rules**.

| Trigger event | Destination | Value |
| - | - | - |
| `lead_qualified` | Meta `Lead` | Set a fixed value: what a qualified lead is worth |
| `deal_won` | Google Ads conversion action | Dynamic, from `value` |
| `deal_won` | Meta `Purchase` | Dynamic, from `value` |

Meta and TikTok optimize on a 7-day window, and most deals close later. Send them `lead_qualified`, and send `deal_won` to Google Ads.

## Verify

1. Open your landing page in a private window with `?utm_source=doctest`.
2. Submit your form with a test email address.
3. Create a test deal for that contact. Move it to your qualified stage.
4. Open **Events**. Confirm a `lead_qualified` row with source `api`.
5. Open **Users**. Find the test email. Confirm the `pageview` and `lead_qualified` sit on one user.
6. Move the deal to **Closed won**. Confirm `deal_won` arrives with the deal amount in `value`.

## When it does not work

| Symptom | Cause | Check | Fix |
| - | - | - | - |
| The request returns `401` | The write key is wrong, or the header name is wrong | The `X-API-Key` header | Copy the `Write key` from **Settings → API** |
| The request returns `400` | The body is not valid JSON, or `event` or `user_id` is empty | The body Zapier or HubSpot sent | Map every placeholder. Remove stray quotes |
| `200` with `Duplicate event detected` | The same `event_id` arrived before | The `event_id` value | Nothing, if it is a retry. Map the real record ID if every request repeats it |
| Events arrive, no campaign on them | DATALYR has never seen this email on a tracked visit | The email in **Users** | Call `identify()` on your form with the email |
| `deal_won` has no value | `amount` is empty on the deal | The deal in HubSpot | Fill in the deal amount |
| `deal_won` shows in revenue reports | `cash_role` was removed from the body | The `properties` object | Restore `"cash_role": "non_cash_fact"` |

## Next

* [Zapier](/integrations/zapier): the same request for any other tool.
* [Ingest API](/api-reference/ingest): every field and status code on `/track`.
* [Lead generation](/use-cases/lead-generation): choose the stage to optimize toward.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.