September 17, 2026

HubSpot API Guide 2026: CRM, CMS & Integration Patterns

HubSpot API Guide 2026: CRM, CMS & Integration Patterns
Table of Contents

HubSpot's API surface has grown significantly over the past three years. What was once primarily a CRM API - create contacts, read deals, submit forms - now covers CMS content management, marketing event tracking, custom timeline activities, workflow triggers, custom object schemas, and more. The 2026 date-based versioning system (introduced with the 2026-09 version) adds a new layer of consideration for teams building integrations they expect to maintain over time.

This guide is written for developers and technical marketers building integrations between HubSpot and websites, external databases, or custom tools. It covers the API landscape, authentication options, versioning strategy, the core CRM objects and their relationships, CMS endpoints for content delivery and management, webhook configuration, and the practical patterns that appear most frequently in B2B website and marketing stack integrations.

For how these API capabilities connect to specific integrations with Webflow or Salesforce, see our HubSpot Webflow integration guide and HubSpot Salesforce integration guide.

API Overview and the 2026-09 Versioning System

The API Versioning Model

HubSpot uses two versioning approaches simultaneously, which is a source of confusion for developers encountering them for the first time. (Source: HubSpot developer documentation - API versioning: developers.hubspot.com/docs/api/overview)

Numeric versioning (v1, v2, v3, v4) indicates the generation of an API family. Most current endpoints use v3 as the stable default. CRM associations moved to v4 in 2023. Some legacy endpoints remain on v1 and v2. Numeric version increments typically represent breaking changes to the endpoint structure or data model.

Date-based versioning (2026-09 and later) is HubSpot's newer approach, applied on top of the numeric version. A date-based version pin locks an endpoint's behaviour - the response shape, field names, and default values - to what was defined at that date, regardless of future HubSpot updates. New builds in 2026 should pin to the latest available date-based version to ensure predictable behaviour as HubSpot evolves the API.

To use a date-based version, pass it as a request header:

GET /crm/v3/objects/contacts/{contactId}

Headers:

  Authorization: Bearer {access_token}

  HubSpot-API-Version: 2026-09

Date-based versions are documented at developers.hubspot.com/changelog - verify the latest available version before starting a new build, as new dates are published periodically.

API Families and Endpoint Reference

API Family Base Path What It Covers Stable Version (2026)
CRM Objects /crm/v3/objects/ Contacts, companies, deals, tickets, custom objects - CRUD + search v3 (stable); v2026-09 versioning applied
Associations /crm/v4/associations/ Links between objects (contact↔deal, company↔contact) v4 (stable); replaces v3 associations
Properties /crm/v3/properties/ Property definitions for any CRM object v3 (stable)
Pipelines /crm/v3/pipelines/ Deal and ticket pipeline stages v3 (stable)
Engagements / Activities /crm/v3/objects/emails, notes, calls Logged activities on CRM records v3 (stable)
Forms /marketing/v3/forms/ Form definitions and submission handling v3 (stable)
Marketing Events /marketing/v3/marketing-events/ Webinar, event attendance tracking against contacts v3 (stable)
CMS - Pages /cms/v3/pages/ Site pages, landing pages - draft, publish, metadata v3 (stable)
CMS - Blog /cms/v3/blogs/posts/ Blog post CRUD and publish state v3 (stable)
CMS - HubDB /cms/v3/hubdb/tables/ HubDB table and row management v3 (stable)
Files /files/v3/files/ File manager uploads, folder management v3 (stable)
Webhooks / Subscriptions /webhooks/v3/ Event subscriptions for CRM object changes v3 (stable)
OAuth /oauth/v1/token Token exchange and refresh v1 (stable)
Timeline Events /crm/v3/timeline/events/ Custom activity types on contact/company/deal records v3 (stable)

All API endpoints use the base URL: 

https://api.hubapi.com

Full paths are appended to this base. For example, to list contacts: 

GET https://api.hubapi.com/crm/v3/objects/contacts

Authentication: Private Apps vs OAuth

Private App Tokens (Recommended for Server-Side Integrations)

Private apps are HubSpot's preferred authentication method for server-to-server integrations - a backend that reads or writes to one specific HubSpot portal. A private app generates a long-lived access token (Bearer token) that is scoped to the permissions selected during app creation. Unlike legacy API keys, private app tokens are scoped, revocable, and auditable.

To create a private app:

  1. In HubSpot, go to Settings > Integrations > Private Apps.
  2. Click Create Private App and give it a descriptive name.
  3. On the Scopes tab, select only the permissions your integration requires - do not grant broad access by default.
  4. Click Create App and copy the generated access token.
  5. Store the token in an environment variable, not in source code.

Use the token in every API request:

Authorization: Bearer {your_private_app_token}

Private app tokens do not expire on a schedule but can be manually rotated in HubSpot's settings. Rotate tokens if a developer with access leaves the team or if the token is accidentally exposed.

OAuth 2.0 (Required for Multi-Portal Applications)

OAuth is required for integrations where the application will access multiple HubSpot portals - a Shopify app, a Webflow plugin, an agency tool, or any publicly distributed integration. OAuth issues short-lived access tokens (valid for 30 minutes) and refresh tokens (valid for 6 hours by default) that must be exchanged to maintain access.

The OAuth flow:

  1. Redirect the user to HubSpot's authorization URL with your client_id, requested scopes, and redirect_uri.
  2. The user grants access in HubSpot; HubSpot redirects back to your redirect_uri with a code parameter.
  3. Exchange the code for tokens via POST to /oauth/v1/token.
  4. Store both the access_token and refresh_token securely.
  5. When the access_token expires, exchange the refresh_token for a new access_token via POST to /oauth/v1/token with grant_type=refresh_token.

Token exchange example:

POST https://api.hubapi.com/oauth/v1/token

Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code

&client_id={your_client_id}

&client_secret={your_client_secret}

&redirect_uri={your_redirect_uri}

&code={code_from_redirect}

Scope Selection: Minimum Required Permissions

Scope selection is a security and maintainability decision. Granting only the permissions the integration requires means a compromised token cannot perform unexpected actions, and the permission set clearly documents what the integration does.

Common Scope What It Grants Required For
crm.objects.contacts.read Read contact records and properties Fetching contact data, checking lead status
crm.objects.contacts.write Create and update contact records Form-to-CRM sync, lead capture, contact enrichment
crm.objects.deals.read Read deal records, pipeline stages Pipeline reporting, deal status checks
crm.objects.deals.write Create and update deal records Deal creation from custom applications, sales tools
crm.objects.companies.read Read company records Account-level data for B2B personalisation
crm.objects.companies.write Create and update company records Account sync from external systems
crm.schemas.contacts.read Read contact property definitions Dynamic form building, property discovery
forms Read and submit to HubSpot forms Form submission via API (server-side form posting)
content Read and write CMS pages, blogs, HubDB CMS API operations, content delivery, HubDB management
files Read and write file manager assets Programmatic file uploads, asset management
oauth OAuth flow management Required for any OAuth-based integration
webhooks Manage webhook subscriptions Registering and managing event subscriptions

Core CRM Objects: Contacts, Companies, Deals, and Tickets

The CRM Object Model

HubSpot CRM organises data into objects - structured records with properties and associations. The four standard objects are contacts, companies, deals, and tickets. Custom objects (available at Enterprise tier) allow teams to define additional record types specific to their business.

Every object shares the same core API structure: a numeric id, a set of properties (the fields that hold the object's data), and associations (links to records in other objects). The API pattern for reading, creating, updating, and deleting records is consistent across all object types.

Reading a Contact

Fetch a single contact by their HubSpot contact id:

GET /crm/v3/objects/contacts/{contactId}

  ?properties=email,firstname,lastname,lifecyclestage,hs_lead_status

  &associations=deals,companies

The properties parameter specifies which contact properties to return - always specify this explicitly to avoid receiving the full property set (which is large and increases response time). The associations parameter fetches the ids of associated records in a single request.

Upserting a Contact

The upsert pattern - create if not exists, update if it does - is the correct approach for most contact creation in integrations where the same email address may arrive multiple times (from a website form, for example):

POST /crm/v3/objects/contacts

Headers: HubSpot-API-Version: 2026-09

{

  "properties": {

    "email": "alex@example.com",

    "firstname": "Alex",

    "lastname": "Chen",

    "lifecyclestage": "lead"

  },

  "idProperty": "email"

}

Passing idProperty: "email" tells HubSpot to treat email as the unique identifier. If a contact with that email already exists, the call updates the existing record; if not, it creates a new one. Without this, duplicate records are created on every call for the same email address - a common integration mistake.

Batch Operations

For syncing multiple records at once, use the batch endpoints rather than looping individual CRUD calls:

POST /crm/v3/objects/contacts/batch/upsert

Headers: HubSpot-API-Version: 2026-09

{

  "inputs": [

    {

      "idProperty": "email",

      "properties": { "email": "a@example.com", "firstname": "Alex" }

    },

    {

      "idProperty": "email",

      "properties": { "email": "b@example.com", "firstname": "Beth" }

    }

  ]

}

Batch endpoints accept up to 100 records per request. For larger syncs, split the payload into batches of 100 and send sequentially or in controlled parallel. Batch requests count as a single API call toward rate limits, regardless of how many records they contain.

Searching CRM Objects

The search endpoint enables filtering CRM records by property values without fetching and filtering on the client side:

POST /crm/v3/objects/contacts/search

{

  "filterGroups": [

    {

      "filters": [

        {

          "propertyName": "lifecyclestage",

          "operator": "EQ",

          "value": "lead"

        }

      ]

    }

  ],

  "properties": ["email", "firstname", "lifecyclestage"],

  "limit": 100,

  "after": 0

}

The filterGroups array enables AND logic within a group and OR logic between groups - multiple filters in the same filters array are ANDed; multiple filterGroups are ORed. The after parameter provides cursor-based pagination through large result sets.

Associations: Linking Objects

Associations are the relationships between CRM objects - a contact associated with a deal, a company associated with multiple contacts, a deal associated with a line item. HubSpot v4 associations (released 2023) support labelled associations, allowing a single relationship to carry a semantic label (e.g., 'Primary contact', 'Billing contact').

Creating an association between a contact and a deal:

PUT /crm/v4/objects/contacts/{contactId}/associations/deals/{dealId}

[

  {

    "associationCategory": "HUBSPOT_DEFINED",

    "associationTypeId": 4

  }

]

The associationTypeId value 4 is the standard Contact-to-Deal relationship. HubSpot maintains a reference of all standard association type ids at developers.hubspot.com/docs/api/crm/associations. For custom association labels, create a label definition first via POST to /crm/v4/associations/{fromObjectType}/{toObjectType}/labels, then use the returned id in subsequent association calls.

CMS API: Pages, Blog, and HubDB

Fetching Published Pages

The CMS Pages API retrieves site pages and landing pages from a HubSpot portal. For content delivery use cases - pulling HubSpot-managed content into a custom frontend - this endpoint provides the published content:

GET /cms/v3/pages/site-pages

  ?state=PUBLISHED

  &limit=20

  &properties=id,slug,title,publish_date,meta_description

The state parameter filters by content state: PUBLISHED, DRAFT, or SCHEDULED. The properties parameter limits returned fields - always specify for content delivery to reduce payload size.

HubDB: Structured Content via API

HubDB tables can be read via API for use in custom frontends or external applications. This is particularly useful for content types like team directories, resource libraries, or event listings that are managed in HubSpot by non-developers and consumed by a custom-built frontend:

GET /cms/v3/hubdb/tables/{tableIdOrName}/rows

  ?portalId={your_portal_id}

  &limit=100

  &sort=order

Combine HubDB reads with the HubSpot CRM API to build personalised content - for example, fetching a HubDB table of case studies and filtering server-side by the visiting contact's industry property (read from the CRM via contact identification from cookies). See our HubSpot CMS guide 2026 for how HubDB fits into the broader Content Hub architecture.

Form Submission via API

Server-side form submissions - where a backend processes a form and posts the data to HubSpot - use the Forms Submissions API rather than the JavaScript embed:

POST /submissions/v3/integration/submit/{portalId}/{formGuid}

Content-Type: application/json

{

  "fields": [

    { "name": "email", "value": "alex@example.com" },

    { "name": "firstname", "value": "Alex" },

    { "name": "company", "value": "Acme Ltd" }

  ],

  "context": {

    "pageUri": "https://yoursite.com/contact",

    "pageName": "Contact page"

  },

  "legalConsentOptions": {

    "consent": {

      "consentToProcess": true,

      "text": "I agree to the privacy policy"

    }

  }

}

The context.pageUri and context.pageName fields connect the submission to HubSpot's traffic analytics. The legalConsentOptions block is required for GDPR-compliant portals and must match the consent text shown to the user. A successful submission returns 200 and creates or updates the contact record in HubSpot CRM, enrols the contact in any form-triggered workflows, and records the submission in HubSpot's form analytics.

Webhooks: Reacting to CRM Events in Real Time

Webhooks allow external systems to receive notifications when specific events occur in HubSpot - contact created, deal stage changed, property updated - without polling the API. This is the correct pattern for real-time integrations.

Creating a Webhook Subscription

Webhooks are configured per application via the developer account (not the portal):

POST /webhooks/v3/{appId}/subscriptions

{

  "eventType": "contact.propertyChange",

  "propertyName": "lifecyclestage",

  "active": true

}

Common eventType values: contact.creation, contact.propertyChange, deal.creation, deal.propertyChange, deal.deletion. Set a target URL where HubSpot will POST event payloads via Settings in the developer account.

Handling Webhook Payloads

HubSpot sends webhook payloads as arrays (multiple events can be batched in a single delivery). Each event in the array contains the object type, object id, event type, property name, and new value:

[

  {

    "objectType": "CONTACT",

    "objectId": 12345678,

    "eventType": "contact.propertyChange",

    "propertyName": "lifecyclestage",

    "propertyValue": "marketingqualifiedlead",

    "changeSource": "AUTOMATION_PLATFORM",

    "occurredAt": 1718000000000

  }

]

Respond to webhook deliveries with HTTP 200 within 5 seconds - HubSpot retries failed deliveries (no 200 response) up to 10 times with exponential backoff. For processing-intensive handlers, respond 200 immediately and process the payload asynchronously via a queue.

Validate webhook authenticity using the X-HubSpot-Signature header - HubSpot signs each delivery with your app's client secret. Verify the signature before processing the payload to prevent spoofed requests from triggering your handler.

Rate Limits and Error Handling

Rate Limit Structure

HubSpot enforces two concurrent rate limits: (Source: developers.hubspot.com/docs/api/usage-details)

  • Burst limit: 100 requests per 10 seconds. Exceeding this returns a 429 with a Retry-After header indicating when to resume.
  • Daily limit: 500,000 API calls per day (for private apps on paid portals). This is rarely a constraint for typical integration workloads but can be hit by poorly implemented polling loops.

Best practice for rate limit compliance: implement exponential backoff on 429 responses, avoid tight polling loops (use webhooks instead), and use batch endpoints to reduce call count for bulk operations.

Error Reference

HTTP Status Meaning Common Cause Recommended Action
200 OK Request succeeded - Process response normally
201 Created Resource created successfully POST to create endpoint Use returned id for subsequent operations
204 No Content Request succeeded, no body returned DELETE or some PATCH endpoints Confirm operation succeeded; no body to parse
400 Bad Request Invalid request payload Missing required property, wrong data type, invalid enum value Log the full error body - HubSpot returns detailed validationErrors array
401 Unauthorized Authentication failed Expired OAuth token, invalid API key, wrong token scope Refresh OAuth token; verify scope includes required permission
403 Forbidden Authenticated but not permitted Token lacks required scope, or action not allowed on object type Check app scope in HubSpot developer account; verify portal-level permissions
404 Not Found Object does not exist Wrong object id, object deleted, wrong portal Verify object id exists in the portal; handle gracefully
409 Conflict Duplicate or constraint violation Creating a contact with an email that already exists Use upsert endpoint (POST with idProperty=email) instead of create
429 Too Many Requests Rate limit exceeded Burst limit (100 req/10s) or daily limit exceeded Implement exponential backoff; respect Retry-After header
500 Internal Server Error HubSpot server error Transient issue on HubSpot's infrastructure Retry with exponential backoff; if persistent, check HubSpot status page

Practical Error Handling Pattern

Every API call in a production integration should handle errors explicitly. The minimum error handling pattern:

async function callHubSpot(endpoint, options) {

  const res = await fetch(endpoint, options);

  if (res.status === 429) {

    const retryAfter = res.headers.get('Retry-After') || 10;

    await sleep(retryAfter * 1000);

    return callHubSpot(endpoint, options); // retry

  }

  if (!res.ok) {

    const err = await res.json();

    throw new Error(

      `HubSpot API error ${res.status}: ${JSON.stringify(err)}`

    );

  }

  return res.json();

}

Common B2B Website Integration Patterns

Pattern 1: Website Form to CRM Contact

The most common HubSpot integration. A visitor submits a form on a Webflow, Next.js, or custom site; the backend posts the data to HubSpot and creates or updates a contact:

  1. Validate and sanitise form data server-side before posting to HubSpot.
  2. POST to /submissions/v3/integration/submit/{portalId}/{formGuid} with field values, page context, and legal consent options.
  3. Handle the response: on success, redirect or show confirmation; on error, log the full HubSpot error body.
  4. If additional CRM properties need to be set that are not form fields (e.g., lead source UTM parameters, trial start date), follow up with a PATCH to the contact record using the email as the lookup property.

Pattern 2: Deal Creation on Trial Signup

For SaaS teams that want every trial signup to create a HubSpot deal (to track trial-to-paid conversion in the pipeline):

  1. On trial signup event: upsert the contact with idProperty: "email".
  2. Create a deal via POST to /crm/v3/objects/deals with deal name, pipeline, stage, and close date.
  3. Associate the deal with the contact via PUT to /crm/v4/objects/deals/{dealId}/associations/contacts/{contactId}
  4. Optionally associate with a company: search for an existing company by domain via the search endpoint; create if not found; associate with both the contact and the deal.

Pattern 3: Personalised Content Delivery

For sites that personalise content based on the visiting contact's CRM properties - showing different messaging to leads versus customers, or displaying account-specific information:

  1. Identify the visiting contact from a HubSpot cookie (hubspotutk) - pass this cookie value as a parameter in a server-side lookup to the HubSpot Visitor Identification API.
  2. Fetch the identified contact's properties via GET /crm/v3/objects/contacts/{contactId} with the specific properties needed for personalisation.
  3. Return personalised content from the server based on the contact's lifecycle stage, list membership, or custom properties.
  4. Cache the contact property lookup (with a short TTL of 5–15 minutes) to avoid API calls on every page load for active browsing sessions.

Pattern 4: Real-Time CRM Sync via Webhooks

For two-way integrations where changes in HubSpot should be reflected in an external system (a customer portal, a billing system, or a partner tool):

  1. Subscribe to the relevant HubSpot webhook events (e.g., deal.propertyChange on stage, contact.propertyChange on lifecyclestage).
  2. On webhook receipt, validate the signature, respond 200 immediately, and queue the event for processing.
  3. Process the event: look up the relevant record in the external system using the HubSpot object id (stored as a reference field in the external system), apply the change, and log the result.
  4. Handle conflicts: if both systems can update the same field, implement a last-write-wins or source-of-truth logic to prevent sync loops.

Best Practices for HubSpot API Integrations

  • Always specify a date-based version header on new builds. Pin to HubSpot-API-Version: 2026-09 (or the latest available date) on all new integrations to ensure predictable behaviour as HubSpot evolves the API.
  • Use upsert over create for contacts and companies. Duplicate records are the most common data quality problem in HubSpot portals. Using idProperty: "email" on contact creates and idProperty: "domain" on company creates prevents duplicates at the API level.
  • Request only the properties you need. Every API response includes only the properties you explicitly request - always use the properties parameter. Omitting it returns the full property set, which is large, slow, and includes fields your integration does not need.
  • Use batch endpoints for multi-record operations. A batch upsert of 100 contacts counts as one API call. One hundred individual upserts count as one hundred calls. Batch operations are always preferred for data import and sync use cases.
  • Store HubSpot object ids in your external system. Every object HubSpot creates has a unique numeric id. Store this id alongside your own record identifier - it is the most reliable lookup key for subsequent API operations and for tracing records in debugging.
  • Implement webhook signature verification. A webhook endpoint without signature verification will process any POST request that matches the URL pattern. Verify the X-HubSpot-Signature header on every inbound webhook delivery.
  • Log the full error body on non-200 responses. HubSpot's error responses include a message field and often a validationErrors array with field-level details. Logging the full error body makes debugging integration failures significantly faster.

Work with Belt Creative

Belt Creative builds Webflow sites and implements HubSpot for B2B teams - including the API integration layer that connects website forms, product events, and external systems to HubSpot CRM and Content Hub. If you are building a custom HubSpot integration or need your website connected to HubSpot correctly from day one, we can help.

See our work, or get in touch to discuss your integration requirements.

A Note on Sources

HubSpot API overview and versioning: developers.hubspot.com/docs/api/overview. HubSpot CRM objects API: developers.hubspot.com/docs/api/crm/contacts. HubSpot OAuth documentation: developers.hubspot.com/docs/api/oauth/tokens. HubSpot API rate limits: developers.hubspot.com/docs/api/usage-details. All endpoint paths, response structures, and rate limit figures verified against official HubSpot developer documentation, mid-2026. HubSpot's API evolves frequently - verify at developers.hubspot.com before implementation.

Frequently Asked Questions

What Is the Latest HubSpot API Version in 2026?

Most CRM and CMS endpoints are at numeric version v3 (with associations at v4). The latest date-based version available is 2026-09 - pass this as the HubSpot-API-Version request header on new builds to pin your integration to a stable, predictable API behaviour. Check developers.hubspot.com/changelog for any newer date versions published after this guide was written.

How Do I Authenticate with the HubSpot API?

For server-to-server integrations with a single portal, create a private app in HubSpot and use its Bearer token in the Authorization header. For multi-portal applications (publicly distributed tools, apps), implement OAuth 2.0. Legacy API keys (hapikey) are deprecated and should not be used in new builds.

What Are HubSpot's API Rate Limits?

The primary limits are 100 requests per 10-second window (burst) and 500,000 requests per day (daily, for paid portals). Batch endpoints count as a single request regardless of how many records they contain. On 429 responses, back off and retry using the Retry-After header value.

How Do I Prevent Duplicate Contacts When Syncing?

Use the upsert pattern: POST to /crm/v3/objects/contacts with "idProperty": "email" in the request body. This creates a new contact if the email does not exist, or updates the existing record if it does - without creating a duplicate.

How Do I Receive Real-Time HubSpot Events in My Application?

Use HubSpot webhooks. Subscribe to specific event types (contact.creation, deal.propertyChange, etc.) via the webhook subscriptions API, set a target URL in your application, and HubSpot will POST event payloads to that URL when the subscribed events occur. Always validate the X-HubSpot-Signature header to verify the payload is from HubSpot.

Can I Read HubDB Content via API for a Custom Frontend?

Yes. Use GET /cms/v3/hubdb/tables/{tableIdOrName}/rows to fetch rows from any published HubDB table. This allows content managed in HubSpot by non-developers (a team directory, resource library, or event listing) to be consumed by a custom React, Next.js, or other frontend without the frontend being hosted on HubSpot's platform.

What Scopes Do I Need for a Basic Contact Sync Integration?

At minimum: crm.objects.contacts.read and crm.objects.contacts.write. If the integration also creates companies or deals, add the corresponding read/write scopes for those objects. Request only the scopes the integration actively uses.

Sources: developers.hubspot.com/docs/api - all endpoint documentation, versioning, rate limits, and authentication details verified mid-2026.