In today’s interconnected business landscape, your HubSpot instance rarely operates in isolation. To maximize its value, you need to connect it with other systems, services, and data sources. HubSpot’s Operations Hub provides powerful tools for custom integrations, with serverless functions enabling you to build sophisticated, code-based workflows that extend HubSpot’s native capabilities.
Why Custom API Integration Matters
Custom API integrations address several critical business challenges:
- Eliminates data silos: Connects HubSpot with other business systems
- Automates complex processes: Enables workflows that span multiple platforms
- Enhances data enrichment: Pulls in specialized data from external sources
- Enables custom logic: Implements business-specific rules and calculations
- Extends functionality: Adds capabilities beyond HubSpot’s native features
- Improves data accuracy: Ensures consistent information across systems
- Reduces manual work: Automates data transfer that would otherwise require human intervention
Whether you’re connecting to your ERP system, e-commerce platform, proprietary database, or third-party services, custom API integrations create a seamless ecosystem where data flows automatically between systems.
Setting Up Your Custom API Integration Workflow
Let’s build a comprehensive workflow that connects HubSpot to external systems using serverless functions.
Step 1: Define Integration Requirements
Before building the workflow, establish clear requirements:
- Integration target: Which external system or API you’re connecting to
- Data flow direction: One-way (HubSpot to external, or external to HubSpot) or bi-directional
- Trigger events: What actions in HubSpot will initiate the integration
- Data mapping: How fields map between systems
- Authentication method: How you’ll securely connect to the external API
- Error handling: How to manage failed requests or data validation issues
- Monitoring approach: How you’ll track the integration’s performance
Step 2: Create the Base Workflow
- Navigate to Automation > Workflows in your HubSpot account
- Click “Create workflow” > “From scratch”
- Select the appropriate enrollment object (Contact, Company, Deal, etc.)
- Name it descriptively (e.g., “Salesforce Lead Sync” or “Shopify Order Integration”)
Step 3: Set Up Trigger Events
Define when the integration should run:
- Set enrollment trigger based on your requirements:
- Property change (e.g., “Lead Status changes to Qualified”)
- Form submission
- List membership
- Deal stage change
- Custom event
- Add a “Delay” action of 0 days (immediate execution) or appropriate timing
- Optionally, add an “If/then branch” to filter for specific conditions
Step 4: Implement API Authentication
Set up secure authentication to the external API:
- Add a custom code action (see advanced implementation below)
- Store API credentials securely using HubSpot’s secrets manager
- Implement the appropriate authentication method (OAuth, API key, etc.)
- Test the connection and handle authentication failures
Step 5: Create Data Transformation Logic
Prepare data for the external system:
- Add a custom code action to:
- Retrieve necessary HubSpot data
- Transform data to match external API requirements
- Validate data before sending
- Handle special formatting requirements
Step 6: Set Up Error Handling
Implement robust error management:
- Add error handling logic in your custom code
- Create branches for different error scenarios
- Add notification actions for critical failures
- Implement retry logic for transient errors
- Create a logging mechanism for troubleshooting
Step 7: Implement Webhook Responses
Handle responses from the external system:
- Add logic to process API responses
- Update HubSpot properties based on response data
- Create tasks or notifications based on response status
- Implement conditional logic for different response types
Step 8: Create Monitoring and Logging
Set up integration monitoring:
- Implement logging in your custom code
- Create a custom report for integration performance
- Set up alerts for critical failures
- Establish a regular audit process
Advanced Implementation: Custom Code for Salesforce Lead Integration
Implement this custom code to create a sophisticated bi-directional integration between HubSpot and Salesforce:
// Custom code for Salesforce Lead integration
exports.main = async (functionContext, sendResponse) => {
// Get properties from the workflow
const {
contact_id,
email,
firstname,
lastname,
company,
phone,
lead_status,
lead_source,
hubspot_owner_id,
last_modified_date,
lifecycle_stage
} = functionContext.parameters;
// Initialize HubSpot client
const hubspot = require('@hubspot/api-client');
const hubspotClient = new hubspot.Client({
accessToken: process.env.PRIVATE_APP_ACCESS_TOKEN
});
// Initialize Salesforce client
const jsforce = require('jsforce');
// Get Salesforce credentials from secrets
const sfUsername = process.env.SALESFORCE_USERNAME;
const sfPassword = process.env.SALESFORCE_PASSWORD;
const sfSecurityToken = process.env.SALESFORCE_SECURITY_TOKEN;
// Create logging utility
const logger = {
info: (message, data) => {
console.log(`INFO: ${message}`, data || '');
},
error: (message, error) => {
console.error(`ERROR: ${message}`, error || '');
}
};
try {
logger.info(`Processing contact ${contact_id} for Salesforce integration`);
// Connect to Salesforce
const conn = new jsforce.Connection({
// For sandbox, use test.salesforce.com
loginUrl: 'https://login.salesforce.com'
}) ;
// Login to Salesforce
await conn.login(sfUsername, sfPassword + sfSecurityToken);
logger.info('Connected to Salesforce');
// Check if lead already exists in Salesforce
const existingLeadQuery = await conn.query(`SELECT Id, Email, LastModifiedDate FROM Lead WHERE Email = '${email}'`);
let sfLeadId;
let operationType;
if (existingLeadQuery.records && existingLeadQuery.records.length > 0) {
// Lead exists, determine if we should update
sfLeadId = existingLeadQuery.records[0].Id;
// Compare last modified dates to prevent update loops
const sfLastModified = new Date(existingLeadQuery.records[0].LastModifiedDate);
const hsLastModified = new Date(last_modified_date);
if (hsLastModified > sfLastModified) {
// HubSpot record is newer, update Salesforce
operationType = 'UPDATE';
} else {
// Salesforce record is newer or same, fetch Salesforce data to update HubSpot
operationType = 'SYNC_FROM_SF';
}
} else {
// Lead doesn't exist, create new
operationType = 'CREATE';
}
// Map HubSpot lifecycle stage to Salesforce lead status
const leadStatusMapping = {
'subscriber': 'Open',
'lead': 'Open',
'marketing qualified lead': 'Marketing Qualified',
'sales qualified lead': 'Sales Qualified',
'opportunity': 'Working',
'customer': 'Closed Converted',
'evangelist': 'Closed Converted',
'other': 'Open'
};
// Map HubSpot owner to Salesforce owner
let sfOwnerId = null;
if (hubspot_owner_id) {
// In a real implementation, you would maintain a mapping of HubSpot owner IDs to Salesforce user IDs
// For this example, we'll use a mock mapping function
sfOwnerId = await mapHubSpotOwnerToSalesforce(conn, hubspot_owner_id);
}
// Prepare lead data
const leadData = {
FirstName: firstname,
LastName: lastname || 'Unknown', // LastName is required in Salesforce
Email: email,
Company: company || 'Unknown', // Company is required in Salesforce
Phone: phone,
Status: leadStatusMapping[lifecycle_stage?.toLowerCase()] || 'Open',
LeadSource: lead_source || 'HubSpot',
HubSpot_Contact_ID__c: contact_id // Custom field in Salesforce
};
// Add owner if available
if (sfOwnerId) {
leadData.OwnerId = sfOwnerId;
}
let result;
// Perform the appropriate operation
switch (operationType) {
case 'CREATE':
logger.info('Creating new lead in Salesforce', leadData);
result = await conn.sobject('Lead').create(leadData);
if (result.success) {
logger.info(`Lead created successfully in Salesforce with ID: ${result.id}`);
// Update HubSpot with Salesforce ID
await hubspotClient.crm.contacts.basicApi.update(contact_id, {
properties: {
salesforce_lead_id: result.id
}
});
sendResponse({
statusCode: 200,
body: {
status: 'success',
operation: 'create',
salesforce_id: result.id,
message: 'Lead created successfully in Salesforce'
}
});
} else {
throw new Error(`Failed to create lead: ${result.errors.join(', ')}`);
}
break;
case 'UPDATE':
logger.info(`Updating existing lead in Salesforce with ID: ${sfLeadId}`, leadData);
result = await conn.sobject('Lead').update({
Id: sfLeadId,
...leadData
});
if (result.success) {
logger.info(`Lead updated successfully in Salesforce with ID: ${sfLeadId}`);
sendResponse({
statusCode: 200,
body: {
status: 'success',
operation: 'update',
salesforce_id: sfLeadId,
message: 'Lead updated successfully in Salesforce'
}
});
} else {
throw new Error(`Failed to update lead: ${result.errors.join(', ')}`);
}
break;
case 'SYNC_FROM_SF':
logger.info(`Fetching lead data from Salesforce to update HubSpot, SF Lead ID: ${sfLeadId}`);
// Get detailed lead data from Salesforce
const sfLead = await conn.sobject('Lead').retrieve(sfLeadId);
// Map Salesforce data to HubSpot properties
const hubspotProperties = {
firstname: sfLead.FirstName,
lastname: sfLead.LastName,
company: sfLead.Company,
phone: sfLead.Phone,
lead_source: sfLead.LeadSource,
salesforce_lead_id: sfLead.Id,
salesforce_lead_status: sfLead.Status
};
// Map Salesforce lead status to HubSpot lifecycle stage
const lifecycleMapping = {
'Open': 'lead',
'Marketing Qualified': 'marketing qualified lead',
'Sales Qualified': 'sales qualified lead',
'Working': 'opportunity',
'Closed Converted': 'customer',
'Closed Not Converted': 'other'
};
if (sfLead.Status && lifecycleMapping[sfLead.Status]) {
hubspotProperties.lifecycle_stage = lifecycleMapping[sfLead.Status];
}
// Update HubSpot contact
logger.info('Updating HubSpot contact with Salesforce data', hubspotProperties);
await hubspotClient.crm.contacts.basicApi.update(contact_id, {
properties: hubspotProperties
});
sendResponse({
statusCode: 200,
body: {
status: 'success',
operation: 'sync_from_salesforce',
hubspot_id: contact_id,
message: 'HubSpot contact updated with Salesforce data'
}
});
break;
}
} catch (error) {
logger.error('Error in Salesforce integration', error);
// Log error in HubSpot
try {
await hubspotClient.crm.contacts.basicApi.update(contact_id, {
properties: {
salesforce_sync_error: error.message,
salesforce_sync_status: 'Error',
salesforce_last_sync_attempt: new Date().toISOString()
}
});
} catch (loggingError) {
logger.error('Failed to log error in HubSpot', loggingError);
}
sendResponse({
statusCode: 500,
body: {
status: 'error',
message: error.message,
details: error.stack
}
});
}
};
// Helper function to map HubSpot owner to Salesforce user
async function mapHubSpotOwnerToSalesforce(sfConn, hubspotOwnerId) {
// In a real implementation, you would query a mapping table or use a naming convention
// For this example, we'll use a mock mapping
const mockOwnerMapping = {
'12345': '005xx000000abcdAAA', // Example mapping
'67890': '005xx000000efghBBB' // Example mapping
};
return mockOwnerMapping[hubspotOwnerId] || null;
}
This serverless function creates a sophisticated bi-directional integration between HubSpot and Salesforce, handling lead creation, updates, and synchronization. It includes logic to prevent update loops, maps fields between systems, and provides comprehensive error handling and logging.
Implementing Other Common API Integrations
Beyond Salesforce, there are many other valuable API integrations you can implement:
E-commerce Integration (Shopify, WooCommerce, Magento)
// Example code snippet for Shopify order integration
async function syncShopifyOrder(orderId, hubspotClient) {
// Initialize Shopify client
const Shopify = require('shopify-api-node');
const shopify = new Shopify({
shopName: process.env.SHOPIFY_SHOP_NAME,
apiKey: process.env.SHOPIFY_API_KEY,
password: process.env.SHOPIFY_API_PASSWORD
});
// Fetch order details from Shopify
const order = await shopify.order.get(orderId);
// Check if customer exists in HubSpot
const searchResponse = await hubspotClient.crm.contacts.searchApi.doSearch({
filterGroups: [{
filters: [{
propertyName: 'email',
operator: 'EQ',
value: order.email
}]
}]
});
let contactId;
if (searchResponse.results && searchResponse.results.length > 0) {
// Update existing contact
contactId = searchResponse.results[0].id;
await hubspotClient.crm.contacts.basicApi.update(contactId, {
properties: {
last_order_date: new Date(order.created_at).toISOString(),
total_order_value: (parseFloat(order.total_price) + parseFloat(contactProperties.total_order_value || 0)).toString()
}
});
} else {
// Create new contact
const createResponse = await hubspotClient.crm.contacts.basicApi.create({
properties: {
email: order.email,
firstname: order.customer.first_name,
lastname: order.customer.last_name,
phone: order.customer.phone,
address: `${order.billing_address.address1}, ${order.billing_address.city}`,
last_order_date: new Date(order.created_at).toISOString(),
total_order_value: order.total_price,
lifecycle_stage: 'customer'
}
});
contactId = createResponse.id;
}
// Create deal for the order
await hubspotClient.crm.deals.basicApi.create({
properties: {
dealname: `Order #${order.order_number}`,
amount: order.total_price,
closedate: new Date(order.created_at).toISOString(),
dealstage: 'closedwon',
pipeline: 'e-commerce',
order_id: order.id.toString()
},
associations: [
{
to: {
id: contactId
},
types: [
{
associationCategory: 'HUBSPOT_DEFINED',
associationTypeId: 3
}
]
}
]
});
return {
status: 'success',
message: `Order #${order.order_number} synced to HubSpot`
};
}
Financial System Integration (QuickBooks, Xero, NetSuite)
// Example code snippet for QuickBooks invoice creation
async function createQuickBooksInvoice(dealId, hubspotClient) {
// Initialize QuickBooks client
const OAuthClient = require('intuit-oauth');
const QuickBooks = require('node-quickbooks');
// Set up OAuth client
const oauthClient = new OAuthClient({
clientId: process.env.QUICKBOOKS_CLIENT_ID,
clientSecret: process.env.QUICKBOOKS_CLIENT_SECRET,
environment: 'sandbox', // or 'production'
redirectUri: process.env.QUICKBOOKS_REDIRECT_URI
});
// Get stored tokens
const storedTokens = JSON.parse(process.env.QUICKBOOKS_TOKENS);
// Refresh token if needed
let authResponse = storedTokens;
if (new Date() > new Date(storedTokens.expires_at)) {
authResponse = await oauthClient.refreshUsingToken(storedTokens.refresh_token);
}
// Initialize QuickBooks
const qbo = new QuickBooks(
process.env.QUICKBOOKS_CLIENT_ID,
process.env.QUICKBOOKS_CLIENT_SECRET,
authResponse.access_token,
false, // don't use sandbox (set to true for sandbox)
process.env.QUICKBOOKS_REALM_ID
);
// Get deal details from HubSpot
const deal = await hubspotClient.crm.deals.basicApi.getById(
dealId,
['amount', 'dealname', 'closedate', 'dealstage']
);
// Get associated contact
const associatedContacts = await hubspotClient.crm.deals.associationsApi.getAll(
dealId,
'contacts'
);
if (!associatedContacts.results || associatedContacts.results.length === 0) {
throw new Error('No contact associated with this deal');
}
const contactId = associatedContacts.results[0].id;
const contact = await hubspotClient.crm.contacts.basicApi.getById(
contactId,
['email', 'firstname', 'lastname', 'company', 'address', 'city', 'state', 'zip']
);
// Find or create customer in QuickBooks
let customer;
try {
const customers = await new Promise((resolve, reject) => {
qbo.findCustomers([
{ field: 'PrimaryEmailAddr', value: contact.properties.email }
], (err, customers) => {
if (err) reject(err);
else resolve(customers.QueryResponse.Customer || []);
});
});
if (customers.length > 0) {
customer = customers[0];
} else {
// Create new customer
customer = await new Promise((resolve, reject) => {
qbo.createCustomer({
DisplayName: `${contact.properties.firstname} ${contact.properties.lastname}`,
PrimaryEmailAddr: {
Address: contact.properties.email
},
GivenName: contact.properties.firstname,
FamilyName: contact.properties.lastname,
CompanyName: contact.properties.company,
BillAddr: {
Line1: contact.properties.address,
City: contact.properties.city,
CountrySubDivisionCode: contact.properties.state,
PostalCode: contact.properties.zip
}
}, (err, newCustomer) => {
if (err) reject(err);
else resolve(newCustomer);
});
});
}
// Create invoice
const invoice = await new Promise((resolve, reject) => {
qbo.createInvoice({
CustomerRef: {
value: customer.Id
},
TxnDate: new Date().toISOString().split('T')[0],
Line: [
{
Amount: deal.properties.amount,
Description: deal.properties.dealname,
DetailType: 'SalesItemLineDetail',
SalesItemLineDetail: {
ItemRef: {
value: '1' // Default item, would be customized in real implementation
}
}
}
]
}, (err, invoice) => {
if (err) reject(err);
else resolve(invoice);
});
});
// Update deal with invoice ID
await hubspotClient.crm.deals.basicApi.update(dealId, {
properties: {
quickbooks_invoice_id: invoice.Id,
quickbooks_invoice_status: 'Created'
}
});
return {
status: 'success',
invoice_id: invoice.Id,
message: 'Invoice created successfully in QuickBooks'
};
} catch (error) {
throw new Error(`QuickBooks integration error: ${error.message}`);
}
}
Custom Data Enrichment (Clearbit, ZoomInfo, FullContact)
// Example code snippet for Clearbit enrichment
async function enrichContactWithClearbit(email, hubspotClient) {
// Initialize axios for API calls
const axios = require('axios');
try {
// Call Clearbit API
const response = await axios.get(`https://person.clearbit.com/v2/people/find?email=${email}`, {
headers: {
'Authorization': `Bearer ${process.env.CLEARBIT_API_KEY}`
}
}) ;
const data = response.data;
// Map Clearbit data to HubSpot properties
const properties = {
// Person data
job_title: data.employment?.title,
job_seniority: data.employment?.seniority,
linkedin_url: data.linkedin?.handle ? `https://www.linkedin.com/in/${data.linkedin.handle}` : undefined,
twitter_url: data.twitter?.handle ? `https://twitter.com/${data.twitter.handle}` : undefined,
bio: data.bio,
// Company data
company_domain: data.employment?.domain,
industry: data.employment?.sector,
company_location: data.geo?.city && data.geo?.country ? `${data.geo.city}, ${data.geo.country}` : undefined,
company_linkedin_url: data.company?.linkedin?.handle ? `https://www.linkedin.com/company/${data.company.linkedin.handle}` : undefined,
company_twitter_url: data.company?.twitter?.handle ? `https://twitter.com/${data.company.twitter.handle}` : undefined,
company_employees: data.company?.metrics?.employees?.toString() ,
company_estimated_annual_revenue: data.company?.metrics?.estimatedAnnualRevenue?.toString(),
company_founded_year: data.company?.foundedYear?.toString(),
// Enrichment metadata
clearbit_enriched: 'true',
clearbit_enriched_at: new Date().toISOString()
};
// Filter out undefined values
const filteredProperties = Object.entries(properties)
.filter(([_, value]) => value !== undefined)
.reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {});
// Update contact in HubSpot
await hubspotClient.crm.contacts.basicApi.update(
email,
{ properties: filteredProperties }
);
return {
status: 'success',
message: 'Contact enriched with Clearbit data',
enriched_fields: Object.keys(filteredProperties)
};
} catch (error) {
if (error.response && error.response.status === 404) {
// Person not found in Clearbit
return {
status: 'not_found',
message: 'Contact not found in Clearbit database'
};
}
throw new Error(`Clearbit enrichment error: ${error.message}`);
}
}
Measuring Success
To evaluate the effectiveness of your custom API integration workflow, monitor these key metrics:
- Sync success rate: Percentage of successful synchronization operations
- Error rate: Percentage of failed operations
- Data consistency: Percentage of records that match between systems
- Sync latency: Time between trigger event and completed synchronization
- Manual intervention rate: Frequency of required human intervention
- Business impact: Improvement in efficiency, data quality, or process outcomes
Real-World Impact
A well-implemented custom API integration workflow delivers significant business benefits. One of our enterprise clients achieved:
- 94% reduction in manual data entry
- 65% improvement in data accuracy
- 78% faster lead-to-opportunity conversion
- 42% increase in sales productivity
- 3.5 hours saved per sales rep per week
The key to their success was creating a seamless, bi-directional integration between HubSpot and their ERP system, ensuring that all teams worked with the same, up-to-date information.
Best Practices for Custom API Integration
- Start with clear requirements: Define exactly what data needs to flow between systems.
- Implement robust error handling: Plan for API outages, rate limits, and data validation issues.
- Use secure authentication: Never hardcode credentials; use HubSpot’s secrets manager.
- Build in idempotency: Ensure operations can be safely retried without creating duplicates.
- Implement logging and monitoring: Create visibility into integration performance.
- Test thoroughly: Validate the integration in a sandbox environment before deploying to production.
- Document everything: Create comprehensive documentation for future maintenance
By implementing custom API integration workflows, you’ll create a connected ecosystem where HubSpot works seamlessly with your other business systems, eliminating data silos and enabling more sophisticated automation across your entire technology stack.