AgentSkillsCN

hubspot-integration

构建可靠高效的 HubSpot ↔ Airtable 同步工作流。涵盖 API 选型、速率限制、同步架构,以及 n8n 的实施模式。

SKILL.md
--- frontmatter
name: hubspot-integration
description: Build reliable HubSpot ↔ Airtable sync workflows. Covers API selection, rate limits, sync architecture, and n8n implementation patterns.

HubSpot Integration Patterns

Build reliable HubSpot ↔ Airtable sync workflows. This skill covers API selection, rate limits, sync architecture, and n8n implementation.

Quick Reference

TaskAPIEndpoint Pattern
CRUD objectsv3POST /crm/v3/objects/{type}
Search/filterv3POST /crm/v3/objects/{type}/search
Batch upsertv3POST /crm/v3/objects/{type}/batch/upsert
Associations (all)v4POST /crm/v4/associations/{from}/{to}/batch/read
Labeled associationsv4PUT /crm/v4/objects/{type}/{id}/associations/{toType}/{toId}

API Selection

Use v3 for Objects + Search

code
/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.

code
/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.

code
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)

TierBurst (per 10s)Daily
Free/Starter100250,000
Professional190625,000
Enterprise1901,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

code
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:

  1. If Retry-After header exists, wait that duration
  2. Otherwise, if X-HubSpot-RateLimit-Remaining < 10, wait 3 seconds
  3. 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)

javascript
// 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:

javascript
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:

javascript
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:

  1. Field-level diffing: Only write if incoming value differs from existing
  2. "Last Updated By" filter: Exclude changes made by integration user
    code
    hs_updated_by_user_id NOT_EQ {integration_user_id}
    

Reference Files