Claude Code skill pack for HubSpot (30 skills)
Installation
Open Claude Code and run this command:
/plugin install hubspot-pack@claude-code-plugins-plus
Use --global to install for all projects, or --project for current project only.
What It Does
> Claude Code skill pack for HubSpot CRM integration -- 30 skills covering the CRM v3 API, marketing automation, sales pipelines, and production operations.
Skills (10)
Manage 10-100 HubSpot portals for agency clients with credential isolation that prevents cross-portal data contamination, per-portal audit trails for billing and GDPR/CCPA attribution, and a scriptable bulk-onboarding workflow that eliminates one-at-a-time credential setup.
HubSpot Agency Multi-Portal
Overview
Operate a fleet of HubSpot portals for agency clients without cross-portal contamination, attribution loss, or onboarding bottlenecks. This is not a getting-started guide — it is the infrastructure your agency runs on day one with client one and scales to client one hundred without revisiting.
The six production failures this skill prevents:
- Cross-portal credential contamination — a shared
HUBSPOTACCESSTOKENenv var causes API writes intended for Client A to silently land in Client B's CRM. The HubSpot API does not reject the call; it accepts it. Data corruption is silent and may not be discovered for days. Per-portal credential isolation — enforced in code, not convention — is the only fix. - Audit trail gaps — agency billing, SLA compliance, and GDPR/CCPA data-processing agreements all require proof of which API calls were made on behalf of which client. A shared token makes post-hoc attribution impossible. A per-portal structured audit log with portalId, clientSlug, operation, and timestamp makes attribution irrefutable.
- Bulk onboarding bottleneck — onboarding 50 new clients one-at-a-time requires 50 manual credential setups, 50 manual verifications, and 50 opportunities for human error. A scriptable bulk onboarding workflow reads a CSV of client names and tokens, validates each against the account-info endpoint, and seeds the credential store in one pass.
- Token rotation cascade — rotating one client's private-app token in HubSpot does not update any downstream system. With 50 portals, a partial rotation — some systems updated, some not — leaves stale tokens in production for undetermined periods. A per-portal rotation runbook with a cross-system checklist closes the gap.
- Rate-limit aggregation confusion — each portal has its own independent 500K/day quota. An agency analytics system reading all 50 portals is NOT limited to 500K calls total — it has 500K per portal per day, but only if the token used for each portal belongs to that portal. A shared token collapses all quota attribution to one portal, causing artificial exhaustion and incorrect monitoring.
- Compliance reporting ambiguity — under GDPR Article 30 and CCPA, a data processor (the agency) must demonstrate which operations were performed on which controller's (client's) data and when. A shared token makes this demonstration impossible after the fact. Per-portal audit logs with structured fields make it a simple query.
Prerequisites
- Node.js 18+ or Python 3.10+
- One HubSpot private-app token per client portal (Settings → Integrations → Private Apps → Create private app → Auth tab)
- A secret store the credential router can read at startup: AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, or
Authenticate production HubSpot integrations and survive the auth-side.
HubSpot Auth
Overview
Authenticate a service to HubSpot and operate the auth layer in production. This is not a setup walkthrough — it is the auth code your integration runs at 3am when an OAuth token expires mid-batch, when a portal admin removes a scope, when an agency credential router sends a request to the wrong portal, and when on-call needs to rotate a leaked private-app token without dropping in-flight requests.
The six production failures this skill prevents:
- Token expiry storms — OAuth access tokens expire in 1800 seconds. Every concurrent request notices expiry simultaneously, races to refresh, the token endpoint rate-limits at 10 auth calls/10s, the integration cascades to red.
- Daily rate-limit burnout — retry storms on auth failures burn through the 500K daily API call quota before noon. Exponential backoff with jitter is non-optional.
- Scope drift — a portal admin edits the private app's scopes or a connected OAuth app loses authorization. Cached tokens start returning
403. Retrying does not help. - Token leakage in commits —
pat-na1-*private-app tokens are wide-scope and not auto-expiring. A single leaked commit exposes the entire portal. - Multi-portal credential routing — agencies managing 50+ portals cannot use a single
HUBSPOTACCESSTOKEN. Requests sent to the wrong portal silently operate on the wrong data. - OAuth refresh-token decay — HubSpot refresh tokens expire after one year of non-use. Integrations that go idle (seasonal products, paused automations) silently lose access and require user reconnection.
Prerequisites
- Node.js 18+ (examples) or Python 3.10+
- HubSpot account with a private app or a connected OAuth public app
- For private apps: token from Settings → Integrations → Private Apps → your app → Auth tab
- For OAuth: client ID + secret from developer portal, redirect URI registered
- A secret store the runtime can read at startup and on rotation signal (env var, AWS Secrets Manager, GCP Secret Manager, or equivalent)
Instructions
Build in this order. Each section neutralizes one production failure mode.
1. Token-cache pattern (neutralizes expiry storms)
Reactive refresh on 401 is wrong. It doubles latency on the failing request and creates a thundering herd when all concurrent requests notice expiry at the same millisecond. Cache the token in-process and refresh proactively at 80% of TTL, behind a single-flight gate so concurrent callers serialize on one refresh.
This pattern applies to OAuth access tokens only — private-app tokens do not expire.
type Cached = { value: string; expiresAtBulk-migrate CRM data into HubSpot from Salesforce, Pipedrive, or Copper — or export off HubSpot — with field mapping, ID continuity, association re-linking, dedup safety, rate-limit budgeting, and rollback mitigation across 100K+ record datasets.
HubSpot Bulk Migration
Overview
Move CRM data into HubSpot from Salesforce, Pipedrive, or Copper — or extract it back out — without losing cross-system IDs, breaking associations, or flooding the portal with duplicates. This is not a data-mapping worksheet. It is the code, sequencing, and guardrails your migration runs at 2am against 150K records when the daily API quota is finite, HubSpot has no bulk-delete API, and a bad import leaves permanent junk that requires a support ticket to remove.
The six production failures this skill addresses:
- ID continuity loss — source CRM IDs are not preserved in HubSpot. Fix: create a custom
sourcecrmidproperty on every object type before the first record lands, and write the source ID into it during import. - Association re-creation failure — contacts, companies, and deals in the source are associated; batch-importing them independently creates records without associations unless a second pass re-links them after all IDs are known. Fix: import in order (companies → contacts → deals), then re-link in three association passes.
- Import dedup missing existing records — HubSpot's batch upsert deduplicates contacts by email, but only when email matches exactly. Missing or differently-formatted emails create duplicates. Fix: normalize email to lowercase + trimmed before every import; use
batch/upsertnotbatch/createfor contacts. - Field type mismatch — source date fields in
M/D/Yformat fail HubSpot's ISO 8601 validation; multi-picklist values not in HubSpot's allowed enumeration are silently dropped. Fix: run a pre-migration dry-run that validates every field against HubSpot's property schema before writing a single record. - Rate limit exhaustion — 100K contacts at 100/batch equals 1,000 API calls; association re-linking doubles that. Combined with retries, a naive migration burns the 500K daily quota before finishing. Fix: budget calls per run, sleep between burst windows, and use the CSV import API for volumes above 10K.
- Rollback impossibility — HubSpot has no bulk-delete API. Fix: maintain a local
sourceid → hubspotidmapping file throughout the migration sobatch/archivecalls are programmable.
Auth: set HUBSPOTACCESSTOKEN environment variable to a private app token with CRM write scopes. For token caching, rotation, and multi-portal routing see the hubspot-auth skill in this pack.
Prerequisites
- HubSpot private app token with scopes:
crm.objects.contacts.write,crm.objects.companies.write,crm.objects.deals.write,crm.associations.write,crm.schemas.contacts.write - Python 3.10+
Deduplicate HubSpot contacts at production scale — surviving import storms, wrong-winner merges, fuzzy-match blind spots, association orphans, rate-limit exhaustion, and silent merge failures on conflicting lifecycle or opt-out status.
HubSpot Contact Deduplication
Overview
Merge duplicate contacts in HubSpot and operate that process in production, at scale, without data loss. This is not a one-click cleanup guide — it is the logic your pipeline runs when a sales ops team imports 80,000 leads from a tradeshow CSV that already exist in the CRM, when a merge destroys the "winner" contact's email history, when a fuzzy match on "Jon" vs "John" leaves a six-figure deal associated to a ghost record, and when on-call discovers that 40,000 contacts were merged without checking opt-out flags.
The six production failures this skill prevents:
- Import storms creating thousands of exact duplicates — HubSpot enforces email uniqueness only at the property level; the merge API has no dedup-all-at-once endpoint. A 100K-row CSV import where 60% of rows already exist creates 60,000 duplicates that must be found and merged one pair at a time within a 100 req/10s rate envelope.
- Merge destroying the wrong timeline —
POST /crm/v3/objects/contacts/mergerequires aprimaryObjectId. Picking the wrong one demotes the older contact's full activity timeline — calls, emails, form submissions — to the discarded record's history. - Property-based dedup missing fuzzy matches — Email-exact dedup leaves "john@gmail.com" and "jon.smith@googlemail.com" as separate records. Phone dedup leaves "+1 (512) 867-5309" and "5128675309" as separate records. Without normalization your CRM accumulates a shadow population of semantically identical but technically distinct contacts.
- Post-merge association orphans — When a secondary contact has deals, tickets, or company associations, HubSpot re-parents most automatically — but not all. Custom object associations and some third-party-integration links may not follow.
- Rate-limit exhaustion on large catalogs — A 1-million-contact dedup scan requires 10,000 batch reads (2.7 hours at full throughput, before merge calls). Naive single-threaded loops exhaust the 500K daily quota before the search phase finishes.
- Silent merge failures on conflicting lifecycle or opt-out status — The merge API returns 200 even when the resulting contact has
hsemailoptout=trueoverriding the primary's opted-in status. HubSpot's "most recently updated value wins" rule is wrong for compliance flags.
Auth
Authenticate with a private app token (pat-na1-*) or OAuth access token. Pass it on every request:
Authorization: Bearer {your-token}
Manage HubSpot lifecycle stages and list segmentation in production without silently destroying CRM trust.
HubSpot Lifecycle and Lists
Overview
Move contacts through the HubSpot funnel and maintain list integrity in a production system. This is not a setup walkthrough — it is the code your marketing-ops integration runs when a lifecycle stage update would silently regress a Customer back to Subscriber, when a dynamic list's criteria change orphans members who qualified last week, when a static list import references contacts that were deleted from the portal, when an external lead-scoring model creates a second source of truth that fights HubSpot's native scoring, when a list-membership webhook fires but your consumer returns 5xx and HubSpot never retries, and when an agency needs to mirror list membership across two portals that have no native sync API.
The six production failures this skill prevents:
- Lifecycle stage regression — HubSpot's
PATCH /crm/v3/objects/contacts/{id}will set lifecyclestage to any valid value regardless of direction. Setting a Customer back to Subscriber silently destroys funnel attribution, invalidates reporting, and corrupts revenue forecasting. The API returns200 OK. There is no built-in guard. - Dynamic list criteria drift — editing a dynamic list's filter criteria does not immediately re-evaluate existing members against the new rules. Members who no longer qualify remain in the list until the nightly background refresh completes, creating a stale membership window of up to 24 hours that can trigger incorrect nurture emails or suppression failures.
- Static list import orphans — contacts added to a static list via import or
POST /contacts/v1/lists/{listId}/addremain list members even after the underlying contact record is hard-deleted from the CRM. These orphan IDs return errors on any subsequent contact-level API call and pollute downstream sync pipelines. - Lead scoring model disagreement — writing an external score to a custom contact property while HubSpot's native Lead Scoring tool computes its own score creates two competing signals. Sales works from the HubSpot Score field; marketing automation triggers on the custom property. The two scores diverge and nobody knows which one to trust.
- List-membership webhook missed events — HubSpot's webhook system delivers
contact.propertyChangeevents for lifecyclestage updates and list-membership changes via HTTP POST with no retry on 5xx responses. A single downstream outage during a bulk-import window can drop hundreds of membership events permanently with no dead-letter queue or re-delivery mechanism. - Cross-portal list sync — agencies managing multiple HubSpot portals (e.g., a staging portal mirroring a production portal, or two franchisee portals needing shared suppression lists) have no native API to sync list membership between portals. Ma
Sync backend product events into HubSpot contact and company custom properties using idempotent batched updates — the Segment integration pattern without Segment.
HubSpot Product Event Sync
Overview
Push backend product events into HubSpot custom contact and company properties — the core pattern of a Segment-style integration, built directly against the HubSpot CRM API without a CDP middleman. This is not a tutorial on HubSpot setup. It is the code your data pipeline runs at 3am when a product launch generates a 10K events/minute storm, when a network hiccup causes the same batch to be retried and your "total sessions" counter doubles, when a property type mismatch silently truncates numbers, and when your contact lookup fails because a new user signed up ten seconds ago and HubSpot doesn't have them yet.
The six production failures this skill prevents:
- Non-idempotent updates — the same event processed twice (retry after network failure) increments a counter twice. "Last seen" survives duplication; "total sessions" does not. Idempotency keys must be event-level, not request-level.
- Property type mismatch — writing a number to a HubSpot
stringproperty returns HTTP 200 with the value coerced silently. The stored data is wrong; no error surfaces. Validate property types before writing. - Rate-limit burnout from event storms — a product launch generates 10K events/minute. Naive sync exhausts the 100 req/10s burst budget in under two seconds. A token-bucket queue is non-optional.
- Contact not found — the product event carries an email that HubSpot does not have yet. The batch update silently drops the record. Upsert-by-email or auto-create is the correct path.
- Batch partial failure (207 Multi-Status) —
POST /crm/v3/objects/contacts/batch/updatereturns 207 when some records succeed and others fail. Treating 207 as success causes silent data loss at scale. Parse the per-objecterrorsarray. - Custom event vs custom property confusion — HubSpot has two different systems: custom behavioral events (Marketing Hub Enterprise, timeline-visible) and custom contact/company properties (available on all tiers, stored as structured data on the record). Using the wrong mechanism for the use case leads to wrong attribution, missing data, or a surprise $3K/month plan upgrade.
Prerequisites
- Node.js 18+ or Python 3.10+
- HubSpot private app token with scopes:
crm.objects.contacts.read,crm.objects.contacts.write,crm.objects.companies.read,crm.objects.companies.write,crm.schemas.contacts.read,crm.schemas.contacts.write - Custom properties already defined in HubSpot (or use the property-create flow in this skill to create them)
- Your backend event stream: Kafka topic, SQS queue, webhook receiver, or polling loop — the sync layer is transport-agnostic
- A dead-letter store fo
Survive HubSpot API rate limits at production scale.
HubSpot Rate Limit Survival
Overview
Rate-limit your HubSpot integration so it survives production volume without burning the portal's daily quota before lunch. This skill covers the six failure modes that take down integrations at scale and gives you the code to prevent each one.
Key invariant: HubSpot rate limits are portal-scoped, not app-scoped. Every private app and OAuth app in the same portal shares the same daily and per-10s buckets. There is no per-app isolation.
The six production failures this skill prevents:
- Daily quota burnout — a naive sync of 5M contacts at 100 records/call requires 50,000 API calls. A misconfigured parallel worker pool exhausts the 500K/day quota in under 90 minutes, leaving the portal dark for 22 hours.
- Burst limit ignorance — parallelizing 20 concurrent requests saturates the 100 req/10s window instantly. The 429 retry storm then burns the daily budget too. The burst limit and daily quota are two independent counters.
- Ignoring batch APIs —
GET /contacts/{id}costs 1 quota unit and returns 1 record.POST /contacts/batch/readwith 100 IDs costs 1 unit and returns 100 records. Single-record reads waste 99% of available throughput. - Retry-After ignored — a 429 includes
Retry-After: N. Backing off by 1s when N=30 produces 29 consecutive failures. Backing off by 30s when N=1 adds unnecessary latency. Always parse and honor the header exactly. - Daily limit vs per-10s confusion — these are two independent systems. Burning the burst window does not decrement the daily counter. Exhausting the daily counter does not care about the per-10s rate. Conflating them breaks rate-limit logic.
- Operations Hub Enterprise gating — the 100 req/s sustained rate is gated on Ops Hub Enterprise. A Starter account assuming that throughput gets 429s at 10x the expected rate with no clear signal that the limit tier is wrong.
Prerequisites
- Node.js 18+ or Python 3.10+
- HubSpot private app token or OAuth access token
@hubspot/api-client(npm) orhubspot(pip) for SDK-based integrations- For queue-based architecture: Redis 6+ with
bullmq(npm) orcelery+redis-py(Python) - Portal Settings → Integrations → Private Apps to confirm the plan tier
Auth: Every API call requires Authorization: Bearer {token}. For token acquisition and caching, see hubspot-auth skill. This skill assumes a valid token is already available.
Instructions
Build in this order. Steps 1–3 are mandatory. Steps 4–6 apply when volume exceeds ~50,000 calls/day or when multiple apps share the portal.
Step 1: Read rate-limit headers on
Sync HubSpot CRM data to a data warehouse (BigQuery, Snowflake, or Postgres) for analytics and reporting.
HubSpot Warehouse Sync
Overview
Move HubSpot CRM data to BigQuery, Snowflake, or Postgres in a way that survives production — not just the demo. This is not a connector walkthrough. It is the extraction and load code your pipeline runs when a 2M-contact backfill burns through the 500K daily call quota at noon, when CDC misses three days of deal updates because association changes do not update hs_lastmodifieddate, when a portal admin adds a custom property and your warehouse table schema silently drifts, and when a network timeout at record 45,000 causes your retry to insert 100 duplicate rows.
The six production failures this skill prevents:
- Backfill exhausting the daily rate limit before completing — 2M contacts at 100 records/call is 20,000 calls. At 100 calls/10s the math says 33 minutes, but that burns your entire 500K daily quota before noon and takes every other integration down with it. Token bucket rate limiting with a configurable daily ceiling is non-optional.
- CDC missing association changes — HubSpot's
hs_lastmodifieddateis updated when any property on the contact record changes, but not when an association is created or deleted. A contact-to-deal link added by a sales rep is invisible to a lastmodifieddate-based incremental poll. Association CDC requires a separate poll strategy. - Schema drift causing silent extraction failures — when a portal admin adds or removes a custom property, the warehouse table schema and the extraction property list become misaligned. New properties are dropped on the floor. Removed properties cause KeyError on row construction. Neither failure raises an alarm without explicit schema validation.
- Duplicate rows on batch retry — a network failure mid-batch causes the batch to be re-sent. Without a proper upsert key the warehouse gets duplicate rows that are invisible until an analyst notices double-counted revenue. The correct upsert key for contacts is
id(HubSpot object ID), not a composite of name/email. - Large payload failures on associated object inline pulls — contacts with thousands of engagement records cause payload sizes that exceed HTTP response limits when associations are pulled inline. Associations must be fetched in a separate batch read pass.
- Timezone inconsistency in aggregations — HubSpot stores all timestamps as Unix milliseconds in UTC. If the warehouse session timezone is set to a local timezone,
DATE(created_at)aggregations produce different daily totals depending on where the analyst runs the query.
Prerequisites
- Python 3.10+
- HubSpot private app token with scopes:
crm.objects.contacts.read,crm.objects.companies.read,crm.objects.deals.read,crm.associations.read
Build and harden HubSpot v3 webhook handlers that survive production: HMAC-SHA256 signature verification, Redis SET NX deduplication, async batch processing with immediate 200 ACK, dead-letter queuing for permanent failures, and event-ordering guards for property-change streams.
HubSpot Webhook Handlers
Overview
Receive and process HubSpot webhook events reliably at production scale. This is not a walkthrough for getting your first event — it is the handler code your integration runs when HubSpot delivers 100 events in a single payload at 2am, when a misconfigured proxy silently strips your signature header, when a 3-day outage causes events to arrive in a burst after recovery, and when a rapid sequence of property updates arrives reversed because HubSpot sends in delivery order rather than chronological order.
The six production failures this skill prevents:
- Signature verification bypass — skipping or misconfiguring the HMAC-SHA256 check on
X-HubSpot-Signature-v3allows any party to send spoofed webhook payloads to your endpoint. One misconfigured load balancer or proxy that strips theX-HubSpot-Signature-v3header silently disables all security — your handler returns 200 to unauthenticated requests without knowing it. - Duplicate delivery — HubSpot retries unacknowledged webhooks (non-200 response or timeout) for up to 3 days with exponential backoff. If your handler crashes after processing but before responding, the same contact-update event creates duplicate CRM mutations downstream. Redis SET NX on the event ID is the reliable guard.
- No replay API — events are permanently lost after the 3-day retry window — if your handler is down for more than 3 days, HubSpot drops those events permanently. There is no replay endpoint. Dead-letter queues and recovery runbooks are your only mitigation.
- Batch event explosion — a single webhook delivery contains up to 100 events. Synchronous processing of all 100 within the HTTP request context times out (HubSpot timeout: 5 seconds) and returns 5xx, which triggers a retry storm. The correct pattern is to ACK immediately with 200, enqueue the batch, and process asynchronously.
- Property change ordering — HubSpot sends
contact.propertyChangeevents in delivery order, not chronological order. A fast property update followed by a slow one can arrive reversed: your handler sees the newer value first, then overwrites it with the older value. Sequence guards onoccurredAtare required. - List-membership scope mismatch — subscribing to
contact.propertyChangeforlifecyclestagedoes not automatically deliver list-membership changes. Those require a separate subscription to the list-membership event type and a separateoauthscope orcrm.lists.readscope on the app.
Prerequisites
- Node.js 18+ (TypeScript examples) or Python 3.10+
- Express 4.x (or any HTTP server that can expose a raw body buffer for HMAC verification)
- Redis 6+ (for SET NX deduplication)
- A mes
How It Works
Skills trigger automatically when you discuss HubSpot topics:
- "Help me set up HubSpot" triggers
hubspot-install-auth - "Create a contact in HubSpot" triggers
hubspot-hello-world - "I'm getting a 429 error from HubSpot" triggers
hubspot-rate-limits - "Deploy my HubSpot integration" triggers
hubspot-deploy-integration
Ready to use hubspot-pack?
Related Plugins
supabase-pack
Complete Supabase integration skill pack with 30 skills covering authentication, database, storage, realtime, edge functions, and production operations. Flagship+ tier vendor pack.
/plugin install supabase-pack@claude-code-plugins-plus
vercel-pack
Complete Vercel integration skill pack with 30 skills covering deployments, edge functions, preview environments, performance optimization, and production operations. Flagship+ tier vendor pack.
/plugin install vercel-pack@claude-code-plugins-plus
clay-pack
Complete Clay integration skill pack with 30 skills covering data enrichment, waterfall workflows, AI agents, and GTM automation. Flagship+ tier vendor pack.
/plugin install clay-pack@claude-code-plugins-plus
cursor-pack
Complete Cursor integration skill pack with 30 skills covering AI code editing, composer workflows, codebase indexing, and productivity features. Flagship+ tier vendor pack.
/plugin install cursor-pack@claude-code-plugins-plus
exa-pack
Complete Exa integration skill pack with 30 skills covering neural search, semantic retrieval, web search API, and AI-powered discovery. Flagship+ tier vendor pack.
/plugin install exa-pack@claude-code-plugins-plus
firecrawl-pack
Complete Firecrawl integration skill pack with 30 skills covering web scraping, crawling, markdown conversion, and LLM-ready data extraction. Flagship+ tier vendor pack.
/plugin install firecrawl-pack@claude-code-plugins-plus