A HubSpot CRM integration for a SaaS product reads and writes contacts, companies, deals, tickets and custom objects in each customer's HubSpot account through one OAuth app. The API calls are the easy part. The hard part is that no two accounts are shaped the same, so the integration has to learn each account's properties, pipelines, lifecycle stages and association labels at install time instead of assuming HubSpot's defaults. New code should also call HubSpot's date-based API paths such as /crm/objects/2026-09/, because the numbered v1 to v3 paths become unsupported in September 2027 and v4 on March 30, 2027.
This guide is for the SaaS company shipping a customer-facing HubSpot integration, where one bad assumption breaks sync for every customer whose account differs from your test account. If you would rather not staff that work, Appnigma offers done-for-you integration delivery, which its page sums up as "Build your product. We'll build the integrations." The page says the work covers each customer's own objects and field mappings. If you are connecting your own company's HubSpot to Gmail or Zendesk, our guide to HubSpot integrations explained covers the ready-made apps.
Updated October 2026. Checked against HubSpot's developer docs and changelog on October 23, 2026. This update moves every example to date-based API paths and adds the 2027 support deadlines. It also corrects two facts. Custom objects need Enterprise on any hub, not Operations Hub Pro, and the search limit is five requests per second, not four.
Should a new HubSpot integration still call the v3 and v4 APIs?
No. HubSpot's versioning page says that "Starting from March 30th 2026, HubSpot's REST APIs follow a date-based version standard," with a new version every March and September. The version sits in the path, so a contact read moves from /crm/v3/objects/contacts to /crm/objects/2026-09/contacts. Each version is current for six months, then supported, and goes unsupported 18 months after release.
The older paths still work today, but the dates are set. HubSpot's September 15, 2026 changelog sets a September 2027 enforcement date for "the end of support for v1-v3 APIs" and for legacy public and private apps. It adds that v4 support ends "on its own separate timeline" on March 30, 2027. The same post tells builders to "Do not use v3 or v4 as an intermediate step."
That matters more for a marketplace app than for an internal script. The changelog says that a listed app still calling unsupported versions after September 2027 "loses its certification status," and one still on legacy app architecture "is eligible for delisting." Older guides that tell you to use v3 for records and v4 for association labels describe a split that no longer exists. The 2026-09 associations endpoints carry the label model forward.
| What you call | Legacy path | Date-based path (2026-09) |
|---|---|---|
| Read or write records | /crm/v3/objects/contacts | /crm/objects/2026-09/contacts |
| Property definitions | /crm/v3/properties/contacts | /crm/properties/2026-09/0-1 |
| Custom object schemas | /crm/v3/schemas | /crm-object-schemas/2026-09/schemas |
| Association labels | /crm/v4/associations/contacts/deals/labels | /crm/associations/2026-09/contact/deal/labels |
| Pipelines and stages | /crm/v3/pipelines/deals | /crm/pipelines/2026-09/deals |
One change in the 2026-09 version affects writes directly. HubSpot's write validation changelog says that from that version, "HubSpot will enforce admin-configured validation rules on all CRM API write paths." A customer who makes a property conditionally required can now reject a write your integration sent without it, so your error handling needs to surface the property name back to that customer.
Objects, IDs and scopes your app requests
Every CRM object has an objectTypeId. The standard ones are fixed across accounts, and HubSpot's CRM overview lists them. Custom objects get an ID in the form 2- followed by digits, and that ID is different in every account, so it has to be looked up per install.
| Object | objectTypeId | Read scope | Available to |
|---|---|---|---|
| Contacts | 0-1 | crm.objects.contacts.read | All accounts |
| Companies | 0-2 | crm.objects.companies.read | All accounts |
| Deals | 0-3 | crm.objects.deals.read | All accounts |
| Tickets | 0-5 | crm.objects.tickets.read | All accounts |
| Line items | 0-8 | crm.objects.line_items.read | All accounts |
| Custom objects | 2-xxxxxxx (per account) | crm.objects.custom.read | Enterprise accounts only |
The last row drives an install decision. HubSpot's scopes reference lists crm.objects.custom.read and crm.schemas.custom.read under "Available to Enterprise accounts only." If you make them required, a Starter or Professional customer can't install your app at all. Mark them optional instead. HubSpot's OAuth guide says optional scopes are "automatically dropped" when the account lacks the tool, so the install succeeds and your backend switches custom object sync off for that customer. Our HubSpot OAuth scopes guide lists the rest.
Do you need Operations Hub to create HubSpot custom objects?
No. Custom objects need an Enterprise subscription, but it can be Enterprise on any hub. HubSpot's custom objects article lists seven products at the Enterprise tier. The four main hubs are Marketing Hub, Sales Hub, Service Hub and Content Hub. Data Hub, Smart CRM and Revenue Hub complete the list. Operations Hub is now called Data Hub, and its Professional tier does not include custom objects.
For a SaaS integration, this sets how many of your customers will ever have custom objects to sync. A customer on Sales Hub Professional with Marketing Hub Enterprise does have them. A customer on Professional everywhere does not, however large their contact database is. Read the granted scopes at install and branch on that rather than on the customer's plan name, because the scopes are what the API will actually honor.
What changes from one customer's HubSpot account to the next
Your test account runs HubSpot's defaults. Your customers mostly don't. These are the five places where accounts differ in ways that break hardcoded integrations, and all five can be read through the API at install.
Properties. Customers add custom properties, and they change the labels and options of standard ones. The internal name is the stable key. HubSpot's property article says "You cannot edit the property's object type or internal name," so map on name, never on the label a user sees. Enumeration properties carry their allowed values in options, and the properties API rejects a value that isn't one of them.
Pipelines and stages. HubSpot's deals guide says "You must use the internal ID of a deal stage or pipeline when creating a deal via the API." Default stages have readable IDs such as contractsent. Stages a customer creates get generated numeric IDs, per the pipelines guide. A stage map built in your test account is wrong for every customer who added a stage.
Lifecycle stages. Super Admins can rename, reorder, add and delete stages, per HubSpot's lifecycle stage article. The contacts guide lists the default values, from subscriber and lead through customer and evangelist. Any of them can be missing in a given account. The same guide says the API can "only set the value forward in the stage order," so a sync that moves a contact backward has to clear the value first.
Custom objects. Present only on Enterprise accounts, with a per-account objectTypeId. Read them from the schemas endpoint and let the customer choose which to sync.
Association labels. Customers create their own labels, such as "Billing contact" on a contact to company link. HubSpot allows "up to 10 association labels between each object pairing," per the associations schema guide. Custom label IDs are generated per account.
One install-time read covers all five. This sketch uses the 2026-09 paths from HubSpot's docs and assumes your OAuth layer passes in the token and the scopes it was granted.
const API = 'https://api.hubapi.com';
const V = '2026-09';
async function hs(path, token) {
const res = await fetch(`${API}${path}`, {
headers: {
Authorization: `Bearer ${token}`
}
});
if (!res.ok) throw new Error(`HubSpot ${res.status} on ${path}`);
return res.json();
}
export async function discoverAccount(token, grantedScopes) {
const [contactProps, lifecycle, dealPipelines, contactDealLabels] = await Promise.all([
hs(`/crm/properties/${V}/0-1`, token),
hs(`/crm/properties/${V}/0-1/lifecyclestage`, token),
hs(`/crm/pipelines/${V}/deals`, token),
hs(`/crm/associations/${V}/contact/deal/labels`, token),
]);
const customObjects = grantedScopes.includes('crm.schemas.custom.read')
? (await hs(`/crm-object-schemas/${V}/schemas`, token)).results
: [];
return {
contactProperties: contactProps.results.map(({
name, type, options
}) => ({
name, type, options
})),
lifecycleValues: lifecycle.options.map((o) => o.value),
dealStages: dealPipelines.results.flatMap((p) =>
p.stages.map((s) => ({
pipelineId: p.id, stageId: s.id, label: s.label
}))),
contactDealLabels: contactDealLabels.results,
customObjects: customObjects.map(({
objectTypeId, name
}) => ({
objectTypeId, name
})),
};
}Store the result per customer and show it in your setup screen, so the customer maps their stages and labels to your product's concepts once. Then re-read it on a schedule. HubSpot doesn't send a webhook when an admin adds a property option or a pipeline stage, so a nightly refresh is what catches the change before a write fails.
What a first sync costs one customer's API budget
The first sync after a customer installs your HubSpot app, the backfill, is bounded by limits HubSpot publishes. A marketplace OAuth app gets "110 requests every 10 seconds" for each account that installs it, per the usage guidelines. A list request returns up to 100 contacts and batch writes take 100 records. Batch association reads take up to 1,000 record IDs, per the contacts and associations guides.
Worked from those limits, a customer with 200,000 contacts costs this much to sync once.
| Step | Requests | Minimum time at 110 per 10 seconds |
|---|---|---|
| Read all contacts, 100 per page | 2,000 | About 3 minutes |
| Read their company associations, 1,000 per batch | 200 | About 18 seconds |
| Write 200,000 updates back, 100 per batch | 2,000 | About 3 minutes |
| Write 200,000 updates back one at a time | 200,000 | About 5 hours |
Two things follow from the table. Batching is the difference between a backfill that finishes during onboarding and one that runs into the afternoon. And those minutes are the whole budget for that account, so a backfill running at full speed starves your live sync for the same customer unless you reserve headroom for it.
Search is not a shortcut for a backfill. The search guide limits search "to five requests per second per account" and sends no rate limit headers. Any one query is "limited to 10,000 total results," with a 400 error past that. Newly updated records "may take a few moments" to appear in results. Use search for narrow lookups, and the list and batch endpoints for anything that walks a whole object. Our HubSpot API rate limits guide covers the private app tiers.
Keeping records in sync after install
Webhooks replace polling for change detection. Every account that installs your app through OAuth is subscribed to the events your app configures, and HubSpot's webhooks guide says those notifications "will not count against your app's API rate limits." A request can carry up to 100 events, and delivery to one installed account is capped at 10 concurrent requests.
HubSpot will "re-send failed notifications up to 10 times," spread over 24 hours. A timeout longer than five seconds counts as a failure, and so does any 4xx or 5xx response. The safe handler acknowledges fast, queues the events, and processes them idempotently by event ID, because a slow handler sees the same event again. Validate the X-HubSpot-Signature-v3 header and reject requests whose timestamp is "older than 5 minutes," per the request validation guide. Our HubSpot webhooks implementation guide walks through the handler.
Webhooks don't cover everything. The generic webhook docs note that object.associationChange does not fire for custom objects or user-defined labels, so a nightly reconcile of associations is still needed if those matter to your product.
For writes, batch upsert avoids a read before every write. A POST to /crm/objects/2026-09/contacts/batch/upsert with an idProperty creates or updates in one call. Use a custom unique property holding your own record ID where you can. The contacts guide warns that "Partial upserts are not supported when using email as the idProperty," and HubSpot allows "up to ten unique ID properties per object."
Writing associations without losing a customer's labels
An unlabeled association uses the default path, for example PUT /crm/objects/2026-09/contact/12345/associations/default/company/67891. A labeled one sends the label in the body. To link a contact to a deal with a customer's own label, read the labels first from /crm/associations/2026-09/contact/deal/labels, take the typeId, then send this to /crm/objects/2026-09/contact/{objectId}/associations/deal/{toObjectId}.[
{
"associationCategory": "USER_DEFINED",
"associationTypeId": 36
}
]The 36 comes from that account's label list and will differ in the next account. HubSpot's own labels use the category HUBSPOT_DEFINED with fixed IDs. Direction matters for those. The associations guide gives contact to primary company as type 1, while company to primary contact is type 2.
Deletes are where labels get lost. The same guide warns that "if you delete the default unlabeled association type, this will remove all other associations" between the two records. An integration that clears and rewrites associations on every sync wipes the labels the customer's team set by hand. Diff the labels you intend to change and remove only those.
Mistakes that break a HubSpot CRM integration after install
These failures pass every test in a default account and appear only in a customer's.
- Overwriting multi-select values. A multiple checkbox property replaces its value on write. HubSpot's properties guide says to "add a semicolon before the first value" to append instead.
- Sending dates with a time. A
dateproperty needs midnight UTC as epoch milliseconds, or an ISO date. A local timestamp lands on the wrong day for some customers. - Moving lifecycle stage backward. The write is ignored unless you clear the value first, so the record looks synced but isn't.
- Hardcoding stage IDs. A stage the customer added has a numeric ID your map doesn't contain, and deal writes fail for that pipeline.
- Letting errors pile up. HubSpot says error responses "shouldn't exceed 5% of your total daily requests," and a listed app must stay under that to be certified. Repeated validation failures from one misconfigured customer count against you.
Where Appnigma fits in a HubSpot CRM integration
Appnigma builds and maintains customer integrations and native HubSpot apps for B2B SaaS teams. On its done-for-you page, a named engineer handles the platform work and "Customer-specific logic," with ongoing maintenance included. If your engineers want to own the code, Appnigma's integration infrastructure starts from default mappings and adds custom objects and workflow rules for individual customers, in its own words. If you also want a listing, the marketplace apps page offers "HubSpot listing review and certification support where eligible." Whichever route you choose, the work has to move to date-based APIs and the current app platform before September 2027.
Frequently Asked Questions
What is the HubSpot API rate limit for a marketplace app?
Each account that installs a marketplace OAuth app gets 110 requests every 10 seconds, and CRM search is limited separately to five requests per second per account, per HubSpot's usage guidelines.
Do HubSpot custom objects need Operations Hub?
No. They need Enterprise on any hub, such as Sales Hub or Data Hub (formerly Operations Hub), per HubSpot's custom objects article.
How many records can one HubSpot batch request handle?
Batch create, update and upsert take 100 records per request. Batch association reads take 1,000 inputs and batch association creates take 2,000.
When do the HubSpot v3 CRM endpoints stop being supported?
v1 to v3 become unsupported in September 2027 and v4 on March 30, 2027, per HubSpot's September 2026 changelog. New code should use date-based paths such as 2026-09.
Can Appnigma build our HubSpot CRM integration?
Yes. Appnigma's done-for-you service builds and maintains customer-facing HubSpot integrations, including each customer's custom objects and field mappings.
Related Articles
- HubSpot OAuth scopes guide
- HubSpot webhooks implementation guide
- HubSpot API rate limits guide
- How to build a HubSpot app
- HubSpot API integration guide
- HubSpot integrations explained
About the author
Sunny Chauhan is the co-founder and CEO of Appnigma AI, which builds and maintains customer integrations and native Salesforce and HubSpot apps for B2B SaaS teams. Before Appnigma, he was on Salesforce's AppExchange team, where he worked with more than 300 SaaS partners. He has spent close to a decade on the Salesforce platform, including work at Zennify, and holds Salesforce certifications including Platform Developer II, Platform App Builder, Administrator, Data Cloud Consultant, and AI Associate.
Sources
- Legacy APIs and apps, what's going unsupported and when, HubSpot Developer Changelog
- Developer platform versioning, HubSpot Developers
- CRM API write validation enforcement, HubSpot Developer Changelog
- Understanding the CRM, HubSpot Developers
- Scopes, HubSpot Developers
- Create custom objects, HubSpot Knowledge Base
- Properties API guide, HubSpot Developers
- Pipelines API guide, HubSpot Developers
- Contacts API guide, HubSpot Developers
- Associations API guide, HubSpot Developers
- Search the CRM, HubSpot Developers
- API usage guidelines and limits, HubSpot Developers
- Webhooks API guide, HubSpot Developers
- Validating requests from HubSpot, HubSpot Developers
- Create and customize lifecycle stages, HubSpot Knowledge Base



