HubSpot Integration Patterns
Build reliable HubSpot ↔ Airtable sync workflows. This skill covers API selection, rate limits, sync architecture, and n8n implementation.
Quick Reference
| Task | API | Endpoint Pattern |
|---|---|---|
| CRUD objects | v3 | POST /crm/v3/objects/{type} |
| Search/filter | v3 | POST /crm/v3/objects/{type}/search |
| Batch upsert | v3 | POST /crm/v3/objects/{type}/batch/upsert |
| Associations (all) | v4 | POST /crm/v4/associations/{from}/{to}/batch/read |
| Labeled associations | v4 | PUT /crm/v4/objects/{type}/{id}/associations/{toType}/{toId} |
API Selection
Use v3 for Objects + Search
/crm/v3/objects/contacts # CRUD /crm/v3/objects/contacts/search # Filter + paginate /crm/v3/objects/contacts/batch/upsert # Bulk writes
Use v4 for Associations
v3 associations only return the primary associated company. v4 returns all associations with labels.
/crm/v4/associations/contacts/companies/batch/read # Batch read (1000 inputs)
/crm/v4/objects/contact/{id}/associations/company/{companyId} # Labeled association
Authentication
Use Private App tokens (Bearer auth). API keys are deprecated.
Authorization: Bearer {ACCESS_TOKEN}
Required scopes for sync:
- •
crm.objects.contacts.read/.write - •
crm.objects.companies.read/.write - •
crm.objects.deals.read/.write - •
crm.schemas.custom.read
Rate Limits
Tiered Limits (Private Apps)
| Tier | Burst (per 10s) | Daily |
|---|---|---|
| Free/Starter | 100 | 250,000 |
| Professional | 190 | 625,000 |
| Enterprise | 190 | 1,000,000 |
Search API: Separate Limit
5 requests/second per token — no rate limit headers returned.
Associations API (v4)
- •Burst: 100-150/10s depending on tier
- •Batch read: 1,000 inputs max
- •Batch create: 2,000 inputs max
Rate Limit Headers
X-HubSpot-RateLimit-Remaining # Requests left in 10s window X-HubSpot-RateLimit-Daily-Remaining # Requests left today Retry-After # Seconds to wait after 429
Backoff Strategy:
- •If
Retry-Afterheader exists, wait that duration - •Otherwise, if
X-HubSpot-RateLimit-Remaining < 10, wait 3 seconds - •On 429 without headers, wait 10 seconds (TEN_SECONDLY_ROLLING policy)
Sync Architecture
Recommended: Hybrid Pattern
- •Webhooks for immediacy (deletions, merges, critical updates)
- •Polling with watermark for completeness (incremental sync)
Watermark Incremental Sync (Read Path)
// Store watermark: last_sync_timestamp (epoch ms)
{
"filterGroups": [{
"filters": [{
"propertyName": "lastmodifieddate", // Note: NOT hs_lastmodifieddate for contacts
"operator": "GT",
"value": "1674758400000"
}]
}],
"sorts": [{ "propertyName": "lastmodifieddate", "direction": "ASCENDING" }],
"limit": 200,
"after": "cursor_from_previous_page",
"properties": ["email", "firstname", "lastname", "hs_object_id"]
}
Critical: Use overlap window (watermark minus 2-5 minutes) to avoid missing records with identical timestamps.
Write Path (Airtable → HubSpot)
Use batch upsert with idProperty for idempotent writes:
POST /crm/v3/objects/contacts/batch/upsert?idProperty=email
{
"inputs": [
{ "id": "user@example.com", "properties": { "firstname": "James" } }
]
}
Avoiding N+1 Queries
Bad: Fetch 100 contacts, then 100 individual association calls = 101 requests
Good: Batch read associations:
POST /crm/v4/associations/contacts/companies/batch/read
{ "inputs": [{ "id": "101" }, { "id": "102" }, ...] } // up to 1000
Bi-Directional Loop Prevention
Prevent infinite sync loops with:
- •Field-level diffing: Only write if incoming value differs from existing
- •"Last Updated By" filter: Exclude changes made by integration user
code
hs_updated_by_user_id NOT_EQ {integration_user_id}
Reference Files
- •references/gotchas.md — Merge handling, deletions, property names, data formats
- •references/n8n-patterns.md — Pagination loops, webhook patterns, rate limiting
- •references/api-reference.md — Code examples for search, batch ops, associations