Appnigma

HubSpot CRM Integrations: Complete Builder's Guide (2026)

hubspot crm integration

Jun 25, 2026

11 min read

HubSpot CRM Integrations: Complete Builder's Guide (2026)

Updated June 24, 2026 by Sunny Chauhan.

Most of what makes HubSpot integrations interesting (or hard) lives in the CRM. Contacts, deals, tickets, the custom objects customers build, the custom properties they add on top, the association graph that links it all. A "HubSpot integration" that doesn't deeply understand the CRM data model is shallow by definition. This guide walks through the CRM data model, the sync patterns that survive real production load, custom object and custom property handling, association management, and the four gotchas that derail first-time builders most often.

Pro Tip

TL;DR HubSpot's CRM API v3 is the workhorse for ISV integrations. The data model is objects (contacts, companies, deals, tickets, custom objects), properties (per-object fields), and associations (links between objects). Build patterns that matter: 1/ Discover custom properties at runtime (don't hardcode), 2/ Handle custom objects gracefully (some portals have them, some don't), 3/ Sync via webhooks for change detection (not polling), 4/ Use batch endpoints for any multi-record operation. Four gotchas: custom property type mismatches, association labels (not just type ids), the search-API stricter rate limit, and lifecycle stage as an enum that changes per portal.

The CRM data model in one diagram

Objects, properties, associations:

``` Contact (object) ├── firstname (standard property) ├── email (standard property) ├── customer_priority_c (custom property, customer-defined) ├── associated with → Company (via contact_to_company) ├── associated with → Deal (via contact_to_deal) └── associated with → Ticket (via contact_to_ticket)

Deal (object) ├── dealname (standard property) ├── amount (standard property) ├── dealstage (standard property, enum, values per-pipeline) ├── associated with → Contact (via deal_to_contact) ├── associated with → Company (via deal_to_company) └── associated with → Line Items (via deal_to_line_item)

Custom Object (e.g., "Subscriptions") ├── customer-defined properties └── associations to standard objects (customer-defined) ```

Three concepts to internalize:

1/ Objects. Standard CRM objects (Contact, Company, Deal, Ticket, Line Item, Quote, Product) plus customer-defined custom objects. Each has a objectTypeId (e.g., 0-1 for Contact, 0-2 for Company).

2/ Properties. Fields on an object. HubSpot's standard properties plus customer-defined custom properties. Properties have types (string, number, enum, date, boolean).

3/ Associations. Links between objects. Have a type (e.g., contact_to_deal) and optionally a label (a customer-defined string like "primary contact" or "decision maker").

The standard CRM objects and what they're for

Contact (0-1): People. The primary record type. Customer demographics, communication preferences, lifecycle stage.

Company (0-2): Organizations. Account-level data.

Deal (0-3): Opportunities in a sales pipeline. Has dealstage, amount, pipeline, etc.

Ticket (0-5): Service-desk records. Has hs_pipeline, hs_pipeline_stage, subject.

Line Item (0-8): Individual items on a deal or quote.

Quote (0-14): Customer-facing quote documents.

Product (0-7): Customer's product catalog.

Custom objects (2-xxxxxxxx): Customer-defined record types. The id is portal-specific.

Most ISV integrations care about Contact, Company, Deal, and sometimes Ticket. Custom objects are a tier-locked feature (Operations Hub Pro or above) so not every customer has them.

Discovering custom properties at runtime

This is gotcha 1 and the most common new-integration mistake.

The naive approach: hardcode the standard properties you need (e.g., firstname, email, lifecyclestage). Works fine until you hit a customer who renamed lifecyclestage to something custom, or who needs you to sync their custom field customer_priority_c.

The right pattern: discover properties at runtime.

``javascript const response = await fetch( 'https://api.hubapi.com/crm/v3/properties/contacts', { headers: { 'Authorization': Bearer ${accessToken} }} ); const { results } = await response.json(); // results is an array of property definitions with name, label, type, options ``

Cache the result for 12-24 hours. Properties change rarely; refetching every call is wasteful.

Then in your sync code, use the discovered property names (not hardcoded ones) when reading and writing.

Handling custom objects

Custom objects are powerful and not every portal has them. Three approaches:

Ignore custom objects. Your integration only handles standard objects. Simplest. Loses some customer flexibility.

Detect custom objects at install time. Query /crm/v3/schemas to list all object types in the customer's portal. Show the customer which ones to sync. Adds setup complexity.

Mirror the customer's object model. Bidirectional sync any custom object the customer points your app at. Most flexible. Hardest to build.

For most ISV integrations, ignoring custom objects in v1 is fine. Add support in v2 if customers ask.

To check what custom objects exist in a portal:

``javascript const response = await fetch( 'https://api.hubapi.com/crm/v3/schemas', { headers: { 'Authorization': Bearer ${accessToken} }} ); const { results } = await response.json(); // results includes both standard and custom object schemas ``

Sync patterns that survive production load

Three patterns, applied together, produce sync that doesn't melt at scale:

Pattern A: Webhooks for change detection

Subscribe to webhooks for the CRM objects you sync. When a contact, deal, or ticket changes in the customer's portal, HubSpot pushes the event to your handler. You fetch the changed record (or use the property change payload directly) and propagate to your system.

This replaces polling. Polling at scale is what blows your daily API cap.

See our HubSpot webhooks implementation guide for the full webhook setup.

Pattern B: Batch endpoints for writes

When syncing to HubSpot, accumulate writes and flush in batches:

``javascript const batchResponse = await fetch( 'https://api.hubapi.com/crm/v3/objects/contacts/batch/update', { method: 'POST', headers: { 'Authorization': Bearer ${accessToken}, 'Content-Type': 'application/json' }, body: JSON.stringify({ inputs: contactsToUpdate.slice(0, 100).map(contact => ({ id: contact.id, properties: contact.properties })) }) } ); ``

100 updates per call. 1 rate-limit token. Single-record updates in a loop are an anti-pattern.

Pattern C: Idempotent handlers

HubSpot retries on failure. Your handlers will see the same event multiple times occasionally. Build idempotency:

→ Track processed event ids to skip duplicates → Use upserts not inserts where possible → Make your writes safe to repeat

This isn't optional for production. It's a hygiene baseline.

Association management

Linking a contact to a deal:

`` PUT /crm/v3/objects/contacts/{contactId}/associations/deals/{dealId}/contact_to_deal ``

The path parameter contact_to_deal is the association type. HubSpot has standard association types built in.

Worth knowing: associations also have labels. A contact-to-deal association might have a label like "primary contact" or "billing contact." If your integration needs to express these distinctions, use the labels endpoint:

`` GET /crm/v4/associations/contacts/deals/labels ``

The v4 association API exposes labels that v3 doesn't. For label-aware sync, use v4.

The four gotchas that derail first-time builders

Gotcha 1: Custom property type mismatches

A customer's custom property is type enumeration. Your code treats it as a string. The write API rejects values that aren't valid enum options.

Fix: read the property definition, check type, and validate values against options for enums before writing.

Gotcha 2: Association labels lost when using v3

Sync code reads associations via v3, gets just type ids and not labels. Writes back via v3, losing the customer's labels. Customer complains that "primary contact" became just "contact."

Fix: use v4 association endpoints when labels matter.

Gotcha 3: Search API rate limit hits before general rate limit

Your code uses /crm/v3/objects/contacts/search heavily because the filter logic is convenient. You hit 429s on search even when general API usage is well under 100/10s.

Fix: search API is capped at 4 requests / second per portal. Cache aggressively or precompute filtered datasets.

Gotcha 4: Lifecycle stage enum values change per portal

You hardcode the values "lead", "customer", "evangelist" because those are HubSpot's defaults. Customer X has customized their lifecycle stages to "prospect", "trial", "customer", "expansion." Your sync writes "lead" and breaks.

Fix: read the lifecyclestage property definition's options field for each portal. Use the customer's actual values.

How no-code generation handles CRM integration complexity

At Appnigma we generate Salesforce 2GP Managed Packages that include Apex code respecting the customer's custom fields, custom objects, and sharing rules. The HubSpot equivalent would generate integrations that discover the customer's CRM schema at install time and adapt sync behavior to it. The principle is the same: code shouldn't hardcode customer data shapes; it should discover them.

Until the no-code generators catch up, manual integration building means handling these gotchas yourself. The patterns above are the ones I'd ship.

Pre-flight checklist before deploying a CRM integration

  • [ ] Discovered the customer's custom properties at runtime (not hardcoded) → Yes / No

  • [ ] Cached property schema with reasonable TTL (12-24 hours) → Yes / No

  • [ ] Decided custom object handling (ignore, detect, mirror) → Yes / No

  • [ ] Webhook subscriptions for relevant CRM object changes → Yes / No

  • [ ] Batch endpoints used for all multi-record operations → Yes / No

  • [ ] Idempotent handlers (safe to receive same event twice) → Yes / No

  • [ ] Used v4 association endpoints if labels matter → Yes / No

  • [ ] Validated enum property values before writing → Yes / No

  • [ ] Search API usage minimized (4 rps cap) or cached → Yes / No

  • [ ] Lifecycle stage values discovered per portal (not hardcoded) → Yes / No

Real-world scenario: a revenue intelligence ISV nails custom property discovery

A revenue intelligence product (Avoma-analog) shipped v1 with hardcoded lifecycle stages and three contact properties. Worked for the first 20 customers because most kept HubSpot defaults.

Customer 21 had customized their lifecycle stages. Sync broke silently for them. Customer's CSM noticed contact stages were stuck. Three days of confused triage followed.

The fix:

→ Read /crm/v3/properties/contacts/lifecyclestage on install → Store the customer's enum values → Map between the customer's values and the product's internal canonical stages → Cache the mapping; refresh on a 24-hour TTL

Same pattern applied to deal stages, ticket pipelines, and the half-dozen custom properties they synced. Sync error rate dropped from 3% to under 0.1%. The fix took two days.

Frequently Asked Questions

What is the HubSpot CRM API?

The HubSpot CRM API v3 is the REST API for HubSpot's CRM objects: contacts, companies, deals, tickets, line items, quotes, products, and custom objects. It's the workhorse for ISV integrations and supports CRUD, batch operations, properties management, and associations (HubSpot CRM API docs).

How do I discover HubSpot custom properties at runtime?

Call GET /crm/v3/properties/{objectType} for each object you sync. The response includes property name, label, type, and (for enums) the allowed options. Cache the result for 12-24 hours; properties rarely change.

Does HubSpot have custom objects?

Yes, customers with Operations Hub Pro or above can define custom objects. Custom objects have portal-specific type ids (format 2-xxxxxxxx) and customer-defined properties and associations. Not every portal has them.

What's the difference between HubSpot CRM v3 and v4 association APIs?

v3 manages associations by type only (e.g., contact_to_deal). v4 adds support for association labels (customer-defined strings like "primary contact"). Use v4 when your sync needs to preserve label semantics; v3 is fine for label-agnostic sync.

How should I detect changes in HubSpot CRM data?

Webhooks. Subscribe to contact.propertyChange, deal.propertyChange, etc. via app webhooks in your Developer Account. HubSpot pushes events when changes happen. Polling instead of webhooks consumes the per-portal rate limit and gives stale data. See our HubSpot webhooks guide.

What's the HubSpot CRM search API rate limit?

4 requests / second per portal. Stricter than the general 100/10s limit. If your integration uses search heavily, cache results or restructure to use direct object reads.

How do I sync custom properties to a HubSpot CRM record?

Call the CRM API update endpoint with the property name in the properties payload. Custom properties work the same as standard properties; the name (e.g., customer_priority_c) is the internal name from property discovery. Validate enum values against the property definition's options.

What's the maximum batch size for HubSpot CRM API writes?

100 records per batch call. Use /batch/create, /batch/update, /batch/read, or /batch/archive endpoints. 100 records = 1 API call (1 rate-limit token).

About the author

Sunny Chauhan is the founder and CEO of Appnigma AI, a no-code platform that generates Salesforce AppExchange-ready Managed Packages from natural-language prompts. He holds Salesforce certifications in Platform Developer II, Platform App Builder, Administrator, Data Cloud Consultant, and AI Associate. Appnigma generates Apex code that respects customer custom fields and sharing rules in Salesforce; the runtime-discovery patterns transfer to HubSpot CRM integration.

Originally published June 24, 2026. Last reviewed June 24, 2026. CRM API patterns and gotchas verified against HubSpot's developer documentation current as of the published date.

Sources

1/ HubSpot Developers, CRM API v3 Reference 2/ HubSpot Developers, Properties API 3/ HubSpot Developers, Custom Objects 4/ HubSpot Developers, v4 Associations API

Which of the four gotchas has tripped up your CRM integration most often, and what would it cost to fix it properly?

Ready to transform your Salesforce experience?

Start exploring the Salesforce Exchange today and discover apps that can take your CRM efficiency to the next level.

decorative section tag

Blog and News

Our Recent Updates