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

  1. Navigate to Automation > Workflows in your HubSpot account
  2. Click “Create workflow” > “From scratch”
  3. Select the appropriate enrollment object (Contact, Company, Deal, etc.)
  4. Name it descriptively (e.g., “Salesforce Lead Sync” or “Shopify Order Integration”)

Step 3: Set Up Trigger Events

Define when the integration should run:
  1. 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
  2. Add a “Delay” action of 0 days (immediate execution) or appropriate timing
  3. Optionally, add an “If/then branch” to filter for specific conditions

Step 4: Implement API Authentication

Set up secure authentication to the external API:
  1. Add a custom code action (see advanced implementation below)
  2. Store API credentials securely using HubSpot’s secrets manager
  3. Implement the appropriate authentication method (OAuth, API key, etc.)
  4. Test the connection and handle authentication failures

Step 5: Create Data Transformation Logic

Prepare data for the external system:
  1. 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:
  1. Add error handling logic in your custom code
  2. Create branches for different error scenarios
  3. Add notification actions for critical failures
  4. Implement retry logic for transient errors
  5. Create a logging mechanism for troubleshooting

Step 7: Implement Webhook Responses

Handle responses from the external system:
  1. Add logic to process API responses
  2. Update HubSpot properties based on response data
  3. Create tasks or notifications based on response status
  4. Implement conditional logic for different response types

Step 8: Create Monitoring and Logging

Set up integration monitoring:
  1. Implement logging in your custom code
  2. Create a custom report for integration performance
  3. Set up alerts for critical failures
  4. 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

  1. Start with clear requirements: Define exactly what data needs to flow between systems.
  2. Implement robust error handling: Plan for API outages, rate limits, and data validation issues.
  3. Use secure authentication: Never hardcode credentials; use HubSpot’s secrets manager.
  4. Build in idempotency: Ensure operations can be safely retried without creating duplicates.
  5. Implement logging and monitoring: Create visibility into integration performance.
  6. Test thoroughly: Validate the integration in a sandbox environment before deploying to production.
  7. 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.