# How to send website leads to HubSpot with the API

> Create a HubSpot private app, then upsert each website lead as a contact by email. The exact request, the scopes you need and how to avoid duplicates.

Source: https://roughfigure.com/blog/send-website-leads-to-hubspot (updated 2026-11-11)

To send website leads to HubSpot, create a **private app** with the `crm.objects.contacts.write` scope, then `POST` each lead to HubSpot’s contacts **batch upsert** endpoint using `email` as the ID property. HubSpot creates the contact if the email is new and updates it if it isn’t, so returning customers don’t turn into duplicates.

## Step 1: create a private app

1. In HubSpot, open **Settings → Integrations → Private Apps** (newer accounts show this under **Development**).
2. Create an app and name it after your website form.
3. Under **Scopes**, tick `crm.objects.contacts.write`, and `crm.objects.contacts.read` if you want to check the key works.
4. Create the app, open **Authentication** and copy the access token.

The token works like a password for your CRM. Keep it on your server, never in website JavaScript.

## Step 2: send the lead

HubSpot now uses date versions in its API paths. The current contacts path looks like this:

```http
POST https://api.hubapi.com/crm/objects/2026-09/contacts/batch/upsert
Authorization: Bearer <your private app token>
Content-Type: application/json

{
  "inputs": [
    {
      "id": "amira.khan@mail.test",
      "idProperty": "email",
      "properties": {
        "email": "amira.khan@mail.test",
        "firstname": "Amira",
        "lastname": "Khan",
        "phone": "+447700900456",
        "zip": "M20 2LS"
      }
    }
  ]
}
```

If you have older code using `/crm/v3/objects/contacts`, it keeps working until HubSpot’s end-of-life date for it, but new connections should use the latest date version.

## Step 3: record where the lead came from

Out of the box, HubSpot fills **Original source** from its own tracking code. Leads sent by the API show as “Offline sources”. To keep the detail, add a custom contact property such as `lead_source_detail` (single-line text) and send it with each lead: “Website estimator”, “QR code on van”, “Google Ads”. Then build a list or report on it. Our guide to [tracking lead sources](https://roughfigure.com/blog/lead-source-tracking-for-trades) explains which details are worth saving.

## Step 4: let HubSpot do the follow-up

With leads arriving as contacts, use a HubSpot workflow (on paid tiers) enrolling contacts when `lead_source_detail` is known, to:

- create a deal in your sales pipeline,
- assign the contact to whoever handles quotes,
- send the customer a short “thanks, we’ll call you” email.

On HubSpot’s free tools, you can still filter contacts by the property and work through them each morning.

## Common mistakes

- **Using `/contacts` (create) instead of upsert.** A second enquiry from the same email returns a 409 conflict and the new details are lost.
- **Putting the token in the browser.** Anyone can read it from the page. Send leads from a server, a form tool or an integration that keeps the token secret.
- **Ignoring 429 replies.** HubSpot limits how fast you can call it. Wait and retry instead of giving up.
- **Mixing up `zip` and a custom postcode field.** HubSpot’s built-in property is `zip`, whatever your country calls it.

## Doing it in RoughFigure

In RoughFigure we’ve made HubSpot a built-in connection, so you don’t write any of this. Go to **Integrations → Send leads to your CRM**, choose **HubSpot**, paste your private app token and click **Connect**. We check the token with HubSpot, and from then on every estimator lead is upserted as a contact with their name, email, phone and postcode.

If HubSpot is down, we keep trying for about four hours, and any lead that fails shows the reason and a **Resend** button. [See the HubSpot integration](https://roughfigure.com/integrations/hubspot).

## Questions

### Does this work on HubSpot’s free plan?

Yes. Private apps and the contacts API are available on free HubSpot accounts. Workflows that act on new contacts need a paid tier.

### Why do my API leads show as “Offline sources”?

HubSpot sets original source from its own website tracking. Contacts created through the API are labelled offline. Save your own source in a custom property.

### Can I create a deal as well as a contact?

Yes, with a second request to the deals endpoint and an association to the contact. A workflow is often easier to maintain.
