How to Integrate HubSpot CRM with Your Headless CMS
Connect HubSpot CRM to a headless CMS so sales teams see the latest product pages, case studies, partner offers, and account-specific content inside the CRM without copy-paste work.
What is HubSpot CRM?
HubSpot CRM is a customer relationship platform used by sales, marketing, and customer success teams to track contacts, companies, deals, tickets, activities, and pipeline data. It includes CRM records, custom properties, lists, workflows, reporting, and APIs for syncing data with external systems. HubSpot is widely used by small and midsize teams, as well as larger companies that want CRM, marketing automation, and service data in one place.
Why integrate HubSpot CRM with a headless CMS?
Sales teams work faster when CRM records include the content that supports a conversation. If a rep opens a HubSpot company record and can see the matching case study, pricing guide, industry landing page, or partner one-pager, they don't need to search Slack, ask marketing, or use a stale PDF from last quarter.,A headless CMS integration helps because CRM data and content data are different, but connected. HubSpot CRM owns contacts, companies, deals, lifecycle stages, owners, and activity history. Your content back end owns product messaging, pages, enablement assets, offers, and localized copy. With Sanity's AI Content Operating System, that content is structured as typed JSON in the Content Lake, so you can send HubSpot exactly the fields it needs, like title, URL, audience, industry, product line, region, and sales stage.,The alternative is manual work. Someone publishes a new customer story, then copies the URL into HubSpot, updates a spreadsheet, tags the sales team, and hopes everyone uses the right asset. That breaks quickly when you have 50 sales assets, 6 regions, 3 product lines, and weekly launches. Webhooks and Functions let you run the sync when content changes, not during a nightly batch job or after someone remembers to update a CRM property.
Architecture overview
A typical HubSpot CRM integration starts with structured content in Sanity's Content Lake, such as sales assets, case studies, product pages, gated offers, or account-specific landing pages. When an editor publishes or updates one of those documents in Sanity Studio, a webhook fires for the matching mutation. The webhook can call a Sanity Function, or an external endpoint, with the document ID and event metadata. That server-side code uses GROQ to fetch only the fields HubSpot CRM needs, including joined data from references, such as product, industry, region, owner, and canonical URL. It then calls HubSpot's CRM API with a private app access token, usually through the official @hubspot/api-client SDK. For example, the Function can search a HubSpot custom object by a sanity_id property, update the matching record if it exists, or create a new CRM object if it doesn't. From there, HubSpot workflows, lists, record associations, and sales views can use that synced content data. The end user sees current content in the place they already work, such as a HubSpot contact, company, deal, or custom object record.
Common use cases
Sales asset sync
Publish a case study or battlecard in Sanity, then create or update a HubSpot custom object record that reps can filter by industry, product, region, and deal stage.
Account-based content
Connect Sanity landing pages to HubSpot companies so target accounts get the right executive brief, ROI page, or implementation guide.
Product interest routing
Map Sanity product pages and gated offers to HubSpot properties that help workflows assign leads to the right product specialist.
Localized sales materials
Sync localized assets from Sanity to HubSpot so reps in North America, EMEA, and APAC don't send the wrong region's copy or pricing context.
Step-by-step integration
- 1
Set up HubSpot CRM access
Create or choose a HubSpot account, then create a private app in HubSpot under Settings, Integrations, Private Apps. Grant the scopes your sync needs, such as crm.objects.contacts.read, crm.objects.contacts.write, crm.objects.companies.read, crm.objects.companies.write, and custom object scopes if you're syncing sales assets. Copy the private app access token and keep it in your server environment, not in browser code.
- 2
Install the SDKs
In your Sanity Function, Next.js API route, or other server runtime, install the HubSpot and Sanity clients with npm install @hubspot/api-client @sanity/client. The HubSpot SDK handles CRM object search, create, and update calls. The Sanity client fetches published content from the Content Lake with GROQ.
- 3
Model the content in Sanity Studio
Create a schema for the content you want sales teams to see in HubSpot. For a salesAsset document, include fields like title, slug, assetType, audience, salesStage, region, product reference, industry reference, canonicalUrl, and hubspotObjectId. Add a sanity_id property to the matching HubSpot custom object so you can safely update the same record after each publish.
- 4
Create the sync trigger
Add a Sanity webhook that fires on publish events for the relevant document types, or use a Sanity Function triggered by content mutations. Use a GROQ filter such as _type == 'salesAsset' to avoid firing the integration for unrelated edits like author bios or navigation labels.
- 5
Push structured content to HubSpot CRM
In the handler, fetch the changed Sanity document with GROQ, shape it into HubSpot properties, search HubSpot by sanity_id, then create or update the CRM object. Keep HubSpot property names stable, and log HubSpot response IDs back to your observability tool or to a Sanity field if your team needs traceability.
- 6
Test with real sales scenarios
Publish one asset, update its title, unpublish it, and test a localized version. In HubSpot, confirm that workflows, lists, filters, and record views behave as expected. Also test failure paths, such as a missing HubSpot token, a deleted custom object property, and a Sanity document with no canonical URL.
Code example
import {createClient} from '@sanity/client';
import HubSpot from '@hubspot/api-client';
import {NextRequest, NextResponse} from 'next/server';
const sanity = createClient({
projectId: process.env.SANITY_PROJECT_ID!,
dataset: process.env.SANITY_DATASET!,
apiVersion: '2025-01-01',
token: process.env.SANITY_READ_TOKEN,
useCdn: false,
});
const hubspot = new HubSpot.Client({
accessToken: process.env.HUBSPOT_PRIVATE_APP_TOKEN!,
});
export async function POST(req: NextRequest) {
const event = await req.json();
const id = event.documentId || event.ids?.updated?.[0] || event.ids?.created?.[0];
if (!id) return NextResponse.json({ok: true});
const asset = await sanity.fetch(
`*[_id == $id && _type == "salesAsset"][0]{
_id, title, assetType, salesStage, region,
"url": canonicalUrl,
"product": product->name,
"industry": industry->name
}`,
{id}
);
if (!asset?.url) return NextResponse.json({skipped: 'Missing URL'});
const objectType = process.env.HUBSPOT_SALES_ASSET_OBJECT_TYPE!;
const properties = {
sanity_id: asset._id,
name: asset.title,
asset_url: asset.url,
asset_type: asset.assetType || '',
sales_stage: asset.salesStage || '',
region: asset.region || '',
product: asset.product || '',
industry: asset.industry || '',
};
const found = await hubspot.crm.objects.searchApi.doSearch(objectType, {
filterGroups: [{filters: [{propertyName: 'sanity_id', operator: 'EQ', value: asset._id}]}],
properties: ['sanity_id'],
limit: 1,
});
const existingId = found.results[0]?.id;
if (existingId) {
await hubspot.crm.objects.basicApi.update(objectType, existingId, {properties});
} else {
await hubspot.crm.objects.basicApi.create(objectType, {properties});
}
return NextResponse.json({ok: true});
}How Sanity + HubSpot CRM works
Build your HubSpot CRM integration on Sanity
Sanity's AI Content Operating System gives you the structured content foundation, real-time event system, and flexible APIs to connect published content with HubSpot CRM.
Start building free →CMS approaches to HubSpot CRM
| Capability | Traditional CMS | Sanity |
|---|---|---|
| Sales asset structure | Assets often live as pages, uploads, or WYSIWYG fields, so HubSpot syncs usually need manual mapping or HTML cleanup. | Schema-as-code models sales assets, references, and CRM mapping fields as typed content that HubSpot can consume directly. |
| Sync timing | Teams often export CSVs, copy links into HubSpot, or run scheduled jobs after publish. | Webhooks and Functions can trigger HubSpot CRM updates on publish events without a separate worker service. |
| Field-level control | Integrations may receive too much page data, then filter it later in custom code. | GROQ can project the exact HubSpot payload, including joined references, in a single query. |
| CRM object mapping | Mapping content to HubSpot contacts, companies, deals, or custom objects is usually custom work for every content type. | Schemas can include HubSpot IDs, sync status, and validation rules so editors and developers can see what will sync. |
| Multi-channel reuse | The website is often the primary output, with CRM sync treated as an extra publishing task. | One structured back end can feed the website, mobile app, HubSpot CRM, sales portals, and production AI agents. |
| Trade-offs | Simple pages are easy to publish, but structured CRM sync gets harder as sales use cases grow. | You get more control, but teams should plan schemas, HubSpot properties, and error handling before connecting production workflows. |
Keep building
Explore related integrations to complete your content stack.
Sanity + Salesforce
Sync structured account pages, sales assets, and product content into Salesforce records and revenue workflows.
Sanity + Pipedrive
Connect content for deal stages, industries, and product lines to Pipedrive so reps can match assets to active opportunities.
Sanity + Gong
Pair structured sales content with Gong conversation data to spot content gaps and improve follow-up materials.