应用简介
HubSpot CRM集成专家模式包括OAuth
---
name: hubspot-integration
description: Expert patterns for HubSpot CRM integration including OAuth
authentication, CRM objects, associations, batch operations, webhooks, and
custom objects. Covers Node.js and Python SDKs.
risk: unknown
source: vibeship-spawner-skills (Apache 2.0)
date_added: 2026-02-27
---
# HubSpot Integration
Expert patterns for HubSpot CRM integration including OAuth authentication,
CRM objects, associations, batch operations, webhooks, and custom objects.
Covers Node.js and Python SDKs.
## Patterns
### OAuth 2.0 Authentication
Secure authentication for public apps
**When to use**: Building public app or multi-account integration
### Template
// OAuth 2.0 flow for HubSpot
import { Client } from "@hubspot/api-client";
// Environment variables
const CLIENT_ID = process.env.HUBSPOT_CLIENT_ID;
const CLIENT_SECRET = process.env.HUBSPOT_CLIENT_SECRET;
const REDIRECT_URI = process.env.HUBSPOT_REDIRECT_URI;
const SCOPES = "crm.objects.contacts.read crm.objects.contacts.write";
// Step 1: Generate authorization URL
function getAuthUrl(): string {
const authUrl = new URL("https://app.hubspot.com/oauth/authorize");
authUrl.searchParams.set("client_id", CLIENT_ID);
authUrl.searchParams.set("redirect_uri", REDIRECT_URI);
authUrl.searchParams.set("scope", SCOPES);
return authUrl.toString();
}
// Step 2: Handle OAuth callback
async function handleOAuthCallback(code: string) {
const response = await fetch("https://api.hubapi.com/oauth/v1/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
redirect_uri: REDIRECT_URI,
code: code,
}),
});
const tokens = await response.json();
// {
// access_token: "xxx",
// refresh_token: "xxx",
// expires_in: 1800 // 30 minutes
// }
// Store tokens securely
await storeTokens(tokens);
return tokens;
}
// Step 3: Refresh access token (before expiry)
async function refreshAccessToken(refreshToken: string) {
const response = await fetch("https://api.hubapi.com/oauth/v1/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "refresh_token",
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
refresh_token: refreshToken,
}),
});
return response.json();
}
// Step 4: Create authenticated client
function createClient(accessToken: string): Client {
const hubspotClient = new Client({ accessToken });
return hubspotClient;
}
### Notes
- Access tokens expire in 30 minutes
- Refresh tokens before expiry
- Store refresh tokens securely
- Rotate tokens every 6 months
### Private App Token
Authentication for single-account integrations
**When to use**: Building internal integration for one HubSpot account
### Template
// Private App Token - simpler for single account
import { Client } from "@hubspot/api-client";
// Create client with private app token
const hubspotClient = new Client({
accessToken: process.env.HUBSPOT_PRIVATE_APP_TOKEN,
});
// Private app tokens don't expire
// But should be rotated every 6 months for security
// Example: Get contacts
async function getContacts() {
try {
const response = await hubspotClient.crm.contacts.basicApi.getPage(
100, // limit
undefined, // after cursor
["firstname", "lastname", "email", "phone"], // properties
);
return response.results;
} catch (error) {
if (error.code === 429) {
// Rate limited - implement backoff
const retryAfter = error.headers?.["retry-after"] || 10;
await sleep(retryAfter * 1000);
return getContacts();
}
throw error;
}
}
// Python equivalent
// from hubspot import HubSpot
//
// client = HubSpot(access_token=os.environ["HUBSPOT_PRIVATE_APP_TOKEN"])
//
// contacts = client.crm.contacts.basic_api.get_page(
// limit=100,
// properties=["firstname", "lastname", "email"]
// )
### Notes
- Private app tokens don't expire
- All private apps share daily rate limit
- Each private app has own burst limit
- Recommended: Rotate every 6 months
### CRM Object CRUD Operations
Create, read, update, delete CRM records
**When to use**: Working with contacts, companies, deals, tickets
### Template
import { Client } from "@hubspot/api-client";
const hubspotClient = new Client({
accessToken: process.env.HUBSPOT_TOKEN,
});
// CREATE contact
async function createContact(data: {
email: string;
firstname: string;
lastname: string;
}) {
const response = await hubspotClient.crm.contacts.basicApi.create({
properties: {
email: data.email,
firstname: data.firstname,
lastname: data.lastname,
},
});
return response;
}
// READ contact by ID
async function getContact(contactId: string) {
const response = await hubspotClient.crm.contacts.basicApi.getById(
contactId,
["firstname", "lastname", "email", "phone", "company"],
);
return response;
}
// UPDATE contact
async function updateContact(contactId: string, properties: object) {
const response = await hubspotClient.crm.contacts.basicApi.update(
contactId,
{ properties },
);
return response;
}
// DELETE contact
async function deleteContact(contactId: string) {
await hubspotClient.crm.contacts.basicApi.archive(contactId);
}
// SEARCH contacts
async function searchContacts(query: string) {
const response = await hubspotClient.crm.contacts.searchApi.doSearch({
query,
limit: 100,
properties: ["firstname", "lastname", "email"],
sorts: [{ propertyName: "createdate", direction: "DESCENDING" }],
});
return response.results;
}
// LIST with pagination
async function getAllContacts() {
const allContacts = [];
let after = undefined;
do {
const response = await hubspotClient.crm.contacts.basicApi.getPage(
100,
after,
["firstname", "lastname", "email"],
);
allContacts.push(...response.results);
after = response.paging?.next?.after;
} while (after);
return allContacts;
}
### Notes
- Use properties param to fetch only needed fields
- Search API has 10k result limit
- Always implement pagination for lists
- Archive (soft delete) vs. GDPR delete available
### Batch Operations
Bulk create, update, or read records efficiently
**When to use**: Processing multiple records (reduce rate limit usage)
### Template
import { Client } from "@hubspot/api-client";
const hubspotClient = new Client({
accessToken: process.env.HUBSPOT_TOKEN,
});
// BATCH CREATE contacts (up to 100 per batch)
async function batchCreateContacts(contacts: Array<{
email: string;
firstname: string;
lastname: string;
}>) {
const inputs = contacts.map((contact) => ({
properties: {
email: contact.email,
firstname: contact.firstname,
lastname: contact.lastname,
},
}));
const response = await hubspotClient.crm.contacts.batchApi.create({
inputs,
});
return response.results;
}
// BATCH UPDATE contacts
async function batchUpdateContacts(
updates: Array<{ id: string; properties: object }>
) {
const inputs = updates.map(({ id, properties }) => ({
id,
properties,
}));
const response = await hubspotClient.crm.contacts.batchApi.update({
inputs,
});
return response.results;
}
// BATCH READ contacts by ID
async function batchReadContacts(
ids: string[],
properties: string[] = ["firstname", "lastname", "email"]
) {
const response = await hubspotClient.crm.contacts.batchApi.read({
inputs: ids.map((id) => ({ id })),
properties,
});
return response.results;
}
// BATCH ARCHIVE contacts
async function batchDeleteContacts(ids: string[]) {
await hubspotClient.crm.contacts.batchApi.archive({
inputs: ids.map((id) => ({ id })),
});
}
// Process large dataset in chunks
async function processLargeDataset(allContacts: any[]) {
const BATCH_SIZE = 100;
const results = [];
for (let i = 0; i < allContacts.length; i += BATCH_SIZE) {
const batch = allContacts.slice(i, i + BATCH_SIZE);
const batchResults = await batchCreateContacts(batch);
results.push(...batchResults);
// Respect rate limits - wait between batches
if (i + BATCH_SIZE < allContacts.length) {
await sleep(100); // 100ms between batches
}
}
return results;
}
### Notes
- Max 100 items per batch request
- Saves up to 80% of rate limit quota
- Batch operations are atomic per item (partial success possible)
- Check response.errors for failed items
### Associations v4 API
Create relationships between CRM records
**When to use**: Linking contacts to companies, deals, etc.
### Template
import { Client, AssociationTypes } from "@hubspot/api-client";
const hubspotClient = new Client({
accessToken: process.env.HUBSPOT_TOKEN,
});
// CREATE association (Contact to Company)
async function associateContactToCompany(
contactId: string,
companyId: string
) {
await hubspotClient.crm.associations.v4.basicApi.create(
"contacts",
contactId,
"companies",
companyId,
[
{
associationCategory: "HUBSPOT_DEFINED",
associationTypeId: AssociationTypes.contactToCompany,
},
]
);
}
// CREATE association (Deal to Contact)
async function associateDealToContact(dealId: string, contactId: string) {
await hubspotClient.crm.associations.v4.basicApi.create(
"deals",
dealId,
"contacts",
contactId,
[
{
associationCategory: "HUBSPOT_DEFINED",
associationTypeId: 3, // deal_to_contact
},
]
);
}
// GET associations for a record
async function getContactCompanies(contactId: string) {
const response = await hubspotClient.crm.associations.v4.basicApi.getPage(
"contacts",
contactId,
"companies",
undefined,
500
);
return response.results;
}
// CREATE association with custom label
async function createLabeledAssociation(
contactId: string,
companyId: string,
labelId: number // Custom association label ID
) {
await hubspotClient.crm.associations.v4.basicApi.create(
"contacts",
contactId,
"companies",
companyId,
[
{
associationCategory: "USER_DEFINED",
associationTypeId: labelId,
},
]
);
}
// BATCH create associations
async function batchAssociateContactsToCompany(
contactIds: string[],
companyId: string
) {
const inputs = contactIds.map((contactId) => ({
_from: { id: contactId },
to: { id: companyId },
types: [
{
associationCategory: "HUBSPOT_DEFINED",
associationTypeId: AssociationTypes.contactToCompany,
},
],
}));
await hubspotClient.crm.associations.v4.batchApi.create(
"contacts",
"companies",
{ inputs }
);
}
// Common association type IDs
// Contact to Company: 1
// Company to Contact: 2
// Deal to Contact: 3
// Contact to Deal: 4
// Deal to Company: 5
// Company to Deal: 6
### Notes
- Requires SDK version 9.0.0+ for v4 API
- Association labels supported for custom relationships
- Use batch API for multiple associations
- HUBSPOT_DEFINED for standard, USER_DEFINED for custom labels
### Webhook Handling
Receive real-time notifications from HubSpot
**When to use**: Need instant updates on CRM changes
### Template
import crypto from "crypto";
import { Client } from "@hubspot/api-client";
// Webhook signature validation
function validateWebhookSignature(
requestBody: string,
signature: string,
clientSecret: string
): boolean {
// For v2 signature (most common)
const expectedSignature = crypto
.createHmac("sha256", clientSecret)
.update(requestBody)
.digest("hex");
return signature === expectedSignature;
}
// Express webhook handler
app.post("/webhooks/hubspot", async (req, res) => {
const signature = req.headers["x-hubspot-signature-v3"] as string;
const timestamp = req.headers["x-hubspot-request-timestamp"] as string;
const requestBody = JSON.stringify(req.body);
// Validate signature
const isValid = validateWebhookSignature(
requestBody,
signature,
process.env.HUBSPOT_CLIENT_SECRET
);
if (!isValid) {
console.error("Invalid webhook signature");
return res.status(401).send("Unauthorized");
}
// Check timestamp (prevent replay attacks)
const timestampAge = Date.now() - parseInt(timestamp);
if (timestampAge > 300000) { // 5 minutes
console.error("Webhook timestamp too old");
return res.status(401).send("Timestamp expired");
}
// Process events - respond quickly!
const events = req.body;
// Queue for async processing
for (const event of events) {
await queue.add("hubspot-webhook", event);
}
// Respond immediately
res.status(200).send("OK");
});
// Async processor
async function processWebhookEvent(event: any) {
const { subscriptionType, objectId, propertyName, propertyValue } = event;
switch (subscriptionType) {
case "contact.creation":
await handleContactCreated(objectId);
break;
case "contact.propertyChange":
await handleContactPropertyChange(objectId, propertyName, propertyValue);
break;
case "deal.creation":
await handleDealCreated(objectId);
break;
case "contact.deletion":
await handleContactDeleted(objectId);
break;
default:
console.log(`Unhandled event: ${subscriptionType}`);
}
}
// Webhook subscription types:
// contact.creation, contact.deletion, contact.propertyChange
// company.creation, company.deletion, company.propertyChange
// deal.creation, deal.deletion, deal.propertyChange
### Notes
- Validate signature before processing
- Respond within 5 seconds
- Queue heavy processing for async
- Max 1000 webhook subscriptions per app
### Custom Objects
Create and manage custom object types
**When to use**: Standard objects don't fit your data model
### Template
import { Client } from "@hubspot/api-client";
const hubspotClient = new Client({
accessToken: process.env.HUBSPOT_TOKEN,
});
// CREATE custom object schema
async function createCustomObjectSchema() {
const schema = {
name: "projects",
labels: {
singular: "Project",
plural: "Projects",
},
primaryDisplayProperty: "project_name",
requiredProperties: ["project_name"],
properties: [
{
name: "project_name",
label: "Project Name",
type: "string",
fieldType: "text",
},
{
name: "status",
label: "Status",
type: "enumeration",
fieldType: "select",
options: [
{ label: "Active", value: "active" },
{ label: "Completed", value: "completed" },
{ label: "On Hold", value: "on_hold" },
],
},
{
name: "budget",
label: "Budget",
type: "number",
fieldType: "number",
},
{
name: "start_date",
label: "Start Date",
type: "date",
fieldType: "date",
},
],
associatedObjects: ["CONTACT", "COMPANY"],
};
const response = await hubspotClient.crm.schemas.coreApi.create(schema);
return response;
}
// CREATE custom object record
async function createProject(data: {
project_name: string;
status: string;
budget: number;
}) {
const response = await hubspotClient.crm.objects.basicApi.create(
"projects", // Custom object name
{ properties: data }
);
return response;
}
// READ custom object by ID
async function getProject(projectId: string) {
const response = await hubspotClient.crm.objects.basicApi.getById(
"projects",
projectId,
["project_name", "status", "budget", "start_date"]
);
return response;
}
// UPDATE custom object
async function updateProject(projectId: string, properties: object) {
const response = await hubspotClient.crm.objects.basicApi.update(
"projects",
projectId,
{ properties }
);
return response;
}
// SEARCH custom objects
async function searchProjects(status: string) {
const response = await hubspotClient.crm.objects.searchApi.doSearch(
"projects",
{
filterGroups: [
{
filters: [
{
propertyName: "status",
operator: "EQ",
value: status,
},
],
},
],
properties: ["project_name", "status", "budget"],
limit: 100,
}
);
return response.results;
}
### Notes
- Custom objects require Enterprise tier
- Max 10 custom objects per account
- Use crm.objects API with object name as parameter
- Can associate with standard and other custom objects
## Sharp Edges
### Rate Limits Vary by App Type and Hub Tier
Severity: HIGH
### 5% Error Rate Threshold for Marketplace Apps
Severity: HIGH
### API Keys Deprecated - Use OAuth or Private App Tokens
Severity: CRITICAL
### OAuth Access Tokens Expire in 30 Minutes
Severity: HIGH
### Webhook Requests Must Be Validated
Severity: CRITICAL
### All List Endpoints Require Pagination
Severity: MEDIUM
### Associations v4 API Has Breaking Changes
Severity: HIGH
### Polling Limited to 100,000 Requests Per Day
Severity: MEDIUM
## Validation Checks
### Hardcoded HubSpot API Key
Severity: ERROR
API keys must never be hardcoded
Message: Hardcoded HubSpot API key detected. Use environment variables. Note: API keys are deprecated - use Private App tokens.
### Hardcoded HubSpot Access Token
Severity: ERROR
Access tokens must use environment variables
Message: Hardcoded HubSpot access token. Use environment variables.
### Hardcoded Client Secret
Severity: ERROR
OAuth client secrets must be secured
Message: Hardcoded client secret. Use environment variables.
### Missing Webhook Signature Validation
Severity: ERROR
Webhook endpoints must validate HubSpot signatures
Message: Webhook endpoint without signature validation. Validate X-HubSpot-Signature-v3.
### Missing Rate Limit Handling
Severity: WARNING
API calls should handle 429 responses
Message: HubSpot API calls without rate limit handling. Implement retry logic with backoff.
### Unthrottled Parallel API Calls
Severity: WARNING
Parallel calls can exceed rate limits
Message: Parallel HubSpot API calls without throttling. Use rate limiter.
### Missing Pagination for List Calls
Severity: WARNING
List endpoints return paginated results
Message: API call without pagination handling. Implement cursor-based pagination.
### Individual Operations in Loop
Severity: INFO
Use batch operations for multiple items
Message: Individual API calls in loop. Consider batch operations for better performance.
### Token Storage Without Expiry
Severity: WARNING
OAuth tokens expire and need refresh logic
Message: Token storage without expiry tracking. Store expiresAt for refresh logic.
### Deprecated API Key Usage
Severity: ERROR
API keys are deprecated
Message: Using deprecated API key. Migrate to Private App token or OAuth 2.0.
## Collaboration
### Delegation Triggers
- user needs email marketing automation -> email-marketing (Beyond HubSpot's built-in email tools)
- user needs custom CRM UI -> frontend (Building portal or dashboard)
- user needs data pipeline -> data-engineer (ETL from HubSpot to warehouse)
- user needs Salesforce integration -> salesforce-development (HubSpot + Salesforce sync)
- user needs payment processing -> stripe-integration (Payments beyond HubSpot quotes)
- user needs analytics dashboard -> analytics-specialist (Custom reporting beyond HubSpot)
## When to Use
- User mentions or implies: hubspot
- User mentions or implies: hubspot api
- User mentions or implies: hubspot crm
- User mentions or implies: hubspot integration
- User mentions or implies: contacts api
## Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
发布日期
5/16/2026
提供方
SkillOPIC
来源类型
导入
sickn33
writing
数据安全
使用 Skill 时,您的对话内容将被发送至 AI 模型进行处理。我们会严格保护您的隐私数据,不会将您的对话内容用于模型训练或分享给第三方。 以下为此 Skill 的数据处理说明。
此 Skill 将处理您的对话输入
您的消息将作为 Prompt 上下文发送至 AI 模型
所有通信均通过加密通道传输
对话记录仅保存在本地
您可以随时清除本地对话历史,清除后数据不可恢复
评分和评价
已验证评分
Skill 信息
了解此 Skill 的详细信息和功能特性
写作研究
文案策划
文件结构
SKILL.md19.7 KB
版本历史
- 公开
- 来源于用户导入
如需详细了解相关要求,请访问帮助中心,或给我们提交反馈信息