From hubspot-pack
Implements HubSpot CRM contact-to-deal workflows: upsert contacts, create companies/deals, advance pipeline stages, log activities.
How this skill is triggered — by the user, by Claude, or both
Slash command
/hubspot-pack:hubspot-core-workflow-aThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
End-to-end workflow: capture a lead, create/update contact, create company, create deal in pipeline, advance deal stages, and log activities. The primary money-path workflow for HubSpot CRM.
End-to-end workflow: capture a lead, create/update contact, create company, create deal in pipeline, advance deal stages, and log activities. The primary money-path workflow for HubSpot CRM.
hubspot-install-auth setupcrm.objects.contacts.write, crm.objects.companies.write, crm.objects.deals.writeimport * as hubspot from '@hubspot/api-client';
const client = new hubspot.Client({
accessToken: process.env.HUBSPOT_ACCESS_TOKEN!,
numberOfApiCallRetries: 3,
});
interface LeadInput {
email: string;
firstName: string;
lastName: string;
company: string;
phone?: string;
source?: string;
}
async function upsertContact(lead: LeadInput): Promise<string> {
// Search for existing contact by email
// POST /crm/v3/objects/contacts/search
const existing = await client.crm.contacts.searchApi.doSearch({
filterGroups: [{
filters: [{ propertyName: 'email', operator: 'EQ', value: lead.email }],
}],
properties: ['firstname', 'lastname', 'email'],
limit: 1,
after: 0,
sorts: [],
});
if (existing.results.length > 0) {
// Update existing contact
const contactId = existing.results[0].id;
await client.crm.contacts.basicApi.update(contactId, {
properties: {
firstname: lead.firstName,
lastname: lead.lastName,
phone: lead.phone || '',
hs_lead_status: 'NEW',
},
});
console.log(`Updated existing contact: ${contactId}`);
return contactId;
}
// Create new contact
const contact = await client.crm.contacts.basicApi.create({
properties: {
email: lead.email,
firstname: lead.firstName,
lastname: lead.lastName,
company: lead.company,
phone: lead.phone || '',
lifecyclestage: 'lead',
hs_lead_status: 'NEW',
},
associations: [],
});
console.log(`Created new contact: ${contact.id}`);
return contact.id;
}
async function findOrCreateCompany(
domain: string,
name: string
): Promise<string> {
// Search by domain
const existing = await client.crm.companies.searchApi.doSearch({
filterGroups: [{
filters: [{ propertyName: 'domain', operator: 'EQ', value: domain }],
}],
properties: ['name', 'domain'],
limit: 1,
after: 0,
sorts: [],
});
if (existing.results.length > 0) {
return existing.results[0].id;
}
const company = await client.crm.companies.basicApi.create({
properties: { name, domain },
associations: [],
});
return company.id;
}
async function createDeal(
contactId: string,
companyId: string,
dealName: string,
amount: number
): Promise<string> {
// First, get pipeline stages to find the right stage ID
// GET /crm/v3/pipelines/deals
const pipelines = await client.crm.pipelines.pipelinesApi.getAll('deals');
const defaultPipeline = pipelines.results.find(p => p.label === 'Sales Pipeline')
|| pipelines.results[0];
const firstStage = defaultPipeline.stages.sort(
(a, b) => Number(a.displayOrder) - Number(b.displayOrder)
)[0];
// POST /crm/v3/objects/deals
const deal = await client.crm.deals.basicApi.create({
properties: {
dealname: dealName,
amount: String(amount),
pipeline: defaultPipeline.id,
dealstage: firstStage.id,
closedate: new Date(Date.now() + 30 * 86400000).toISOString(),
},
associations: [
{
to: { id: contactId },
types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 3 }],
},
{
to: { id: companyId },
types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 5 }],
},
],
});
console.log(`Created deal: ${deal.id} in stage "${firstStage.label}"`);
return deal.id;
}
async function advanceDealStage(dealId: string, stageName: string): Promise<void> {
// Look up stage ID from pipeline
const deal = await client.crm.deals.basicApi.getById(dealId, ['pipeline', 'dealstage']);
const pipelines = await client.crm.pipelines.pipelinesApi.getAll('deals');
const pipeline = pipelines.results.find(p => p.id === deal.properties.pipeline);
const targetStage = pipeline?.stages.find(s => s.label === stageName);
if (!targetStage) {
throw new Error(`Stage "${stageName}" not found in pipeline "${pipeline?.label}"`);
}
await client.crm.deals.basicApi.update(dealId, {
properties: { dealstage: targetStage.id },
});
console.log(`Deal ${dealId} moved to "${stageName}"`);
}
async function logNote(contactId: string, dealId: string, body: string): Promise<void> {
// POST /crm/v3/objects/notes
await client.crm.objects.notes.basicApi.create({
properties: {
hs_note_body: body,
hs_timestamp: new Date().toISOString(),
},
associations: [
{
to: { id: contactId },
types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 202 }],
},
{
to: { id: dealId },
types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 214 }],
},
],
});
}
async function processLead(lead: LeadInput) {
const contactId = await upsertContact(lead);
const domain = lead.email.split('@')[1];
const companyId = await findOrCreateCompany(domain, lead.company);
// Associate contact with company
await client.crm.associations.v4.basicApi.create(
'contacts', contactId, 'companies', companyId,
[{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 1 }]
);
const dealId = await createDeal(
contactId, companyId,
`${lead.company} - New Opportunity`,
10000
);
await logNote(contactId, dealId, `Lead captured from ${lead.source || 'website'}`);
return { contactId, companyId, dealId };
}
| Error | Code | Cause | Solution |
|---|---|---|---|
409 Conflict | 409 | Duplicate email on create | Use search-then-create pattern above |
PIPELINE_STAGE_NOT_FOUND | 400 | Invalid stage ID | Fetch pipeline stages first |
INVALID_ASSOCIATION_TYPE | 400 | Wrong associationTypeId | Check default association types |
PROPERTY_DOESNT_EXIST | 400 | Custom property not created | Create in HubSpot Settings > Properties |
For marketing automation workflows, see hubspot-core-workflow-b.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin hubspot-packGenerates TypeScript examples for HubSpot CRM CRUD on contacts, companies, and deals using @hubspot/api-client. Use for new integrations, setup testing, or API learning.
Build a HubSpot workflow that auto-enriches and stages new contacts upon creation. Sets lifecycle stage, copies company name and industry from associated company, and branches on completeness. Must be built manually in the HubSpot UI due to API limitations.
Automates HubSpot CRM workflows for contacts, companies, deals, tickets, and properties via Rube MCP using Composio toolkit. Guides tool sequences for create/search/update operations.