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
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:
- In HubSpot, go to Settings > Integrations > Private Apps.
- Click Create Private App and give it a descriptive name.
- On the Scopes tab, select only the permissions your integration requires - do not grant broad access by default.
- Click Create App and copy the generated access token.
- 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:
- Redirect the user to HubSpot's authorization URL with your client_id, requested scopes, and redirect_uri.
- The user grants access in HubSpot; HubSpot redirects back to your redirect_uri with a code parameter.
- Exchange the code for tokens via POST to /oauth/v1/token.
- Store both the access_token and refresh_token securely.
- 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.
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
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:
- Validate and sanitise form data server-side before posting to HubSpot.
- POST to /submissions/v3/integration/submit/{portalId}/{formGuid} with field values, page context, and legal consent options.
- Handle the response: on success, redirect or show confirmation; on error, log the full HubSpot error body.
- 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):
- On trial signup event: upsert the contact with idProperty: "email".
- Create a deal via POST to /crm/v3/objects/deals with deal name, pipeline, stage, and close date.
- Associate the deal with the contact via PUT to /crm/v4/objects/deals/{dealId}/associations/contacts/{contactId}
- 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:
- 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.
- Fetch the identified contact's properties via GET /crm/v3/objects/contacts/{contactId} with the specific properties needed for personalisation.
- Return personalised content from the server based on the contact's lifecycle stage, list membership, or custom properties.
- 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):
- Subscribe to the relevant HubSpot webhook events (e.g., deal.propertyChange on stage, contact.propertyChange on lifecyclestage).
- On webhook receipt, validate the signature, respond 200 immediately, and queue the event for processing.
- 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.
- 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.

