HubSpot CRM integration reference — @hubspot/api-client for contact sync, deal management, and OAuth flow in A3 (1 backend utility + OAuth endpoint)
From a3-pluginnpx claudepluginhub trusted-american/marketplace --plugin a3-pluginThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
A3 integrates with HubSpot for CRM synchronization. The integration lives in:
functions/src/utils/hubspot.ts — HubSpot client utility (1 file)functions/src/https/hubspot/oauth.ts — OAuth flow endpoint (1 file)Package: @hubspot/api-client v13.1.0
import { Client } from '@hubspot/api-client';
// With OAuth access token (A3 pattern)
const hubspot = new Client({ accessToken: process.env.HUBSPOT_ACCESS_TOKEN });
// With API key (deprecated but may exist in legacy code)
const hubspot = new Client({ apiKey: process.env.HUBSPOT_API_KEY });
A3 uses OAuth for HubSpot authentication (functions/src/https/hubspot/oauth.ts):
// 1. Redirect user to HubSpot authorization URL
const authUrl = `https://app.hubspot.com/oauth/authorize?` +
`client_id=${CLIENT_ID}&` +
`redirect_uri=${REDIRECT_URI}&` +
`scope=crm.objects.contacts.read%20crm.objects.contacts.write`;
// 2. HubSpot redirects back with authorization code
// 3. Exchange code for access token
const tokenResponse = await hubspot.oauth.tokensApi.create(
'authorization_code',
code,
REDIRECT_URI,
CLIENT_ID,
CLIENT_SECRET
);
// 4. Store tokens for future use
const { accessToken, refreshToken, expiresIn } = tokenResponse;
// 5. Refresh token when expired
const refreshResponse = await hubspot.oauth.tokensApi.create(
'refresh_token',
undefined,
undefined,
CLIENT_ID,
CLIENT_SECRET,
refreshToken
);
const response = await hubspot.crm.contacts.basicApi.create({
properties: {
email: 'john@example.com',
firstname: 'John',
lastname: 'Doe',
phone: '5551234567',
company: 'Trusted American',
},
});
const contactId = response.id;
await hubspot.crm.contacts.basicApi.update(contactId, {
properties: {
phone: '5559876543',
lifecyclestage: 'customer',
},
});
const contact = await hubspot.crm.contacts.basicApi.getById(contactId, [
'email', 'firstname', 'lastname', 'phone', 'lifecyclestage',
]);
const searchResponse = await hubspot.crm.contacts.searchApi.doSearch({
filterGroups: [{
filters: [{
propertyName: 'email',
operator: 'EQ',
value: 'john@example.com',
}],
}],
properties: ['email', 'firstname', 'lastname'],
limit: 10,
});
// Batch create
await hubspot.crm.contacts.batchApi.create({
inputs: contacts.map(c => ({
properties: { email: c.email, firstname: c.firstName, lastname: c.lastName },
})),
});
// Create deal
const deal = await hubspot.crm.deals.basicApi.create({
properties: {
dealname: 'Medicare Enrollment - John Doe',
pipeline: 'default',
dealstage: 'appointmentscheduled',
amount: '500',
},
});
// Associate deal with contact
await hubspot.crm.deals.associationsApi.create(
deal.id, 'contacts', contactId, [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 3 }]
);
// In a Firestore trigger:
export const onClientCreated = onDocumentCreated('clients/{clientId}', async (event) => {
const data = event.data?.data();
if (!data) return;
// Sync to HubSpot
await hubspot.crm.contacts.basicApi.create({
properties: {
email: data.email,
firstname: data.firstName,
lastname: data.lastName,
phone: data.phone,
},
});
});
try {
await hubspot.crm.contacts.basicApi.create({ properties: { email } });
} catch (error) {
if (error.code === 409) {
// Contact already exists — update instead
} else if (error.code === 429) {
// Rate limited — retry after delay
} else {
Sentry.captureException(error);
}
}