DEV Community

Cover image for How to Use HubSpot API in 2026
Wanda
Wanda

Posted on • Originally published at apidog.com

How to Use HubSpot API in 2026

TL;DR

The HubSpot API lets you integrate programmatically with HubSpot’s CRM, marketing, sales, and service hubs. It supports OAuth 2.0 and private app authentication, exposes RESTful endpoints for contacts, companies, deals, tickets, and more, and enforces rate limits by subscription tier. This guide shows you how to set up authentication, use core endpoints, configure webhooks, and build robust production integrations.

Try Apidog today

Introduction

HubSpot manages 194,000+ customer accounts and billions of CRM records. If you’re building CRM integrations, marketing automation, or sales tools, HubSpot API integration is essential for accessing 7 million+ users.

Manual data entry between systems costs businesses 15-20 hours weekly. With HubSpot API, automate contact sync, deal updates, marketing workflow triggers, and cross-platform reporting.

💡 Apidog simplifies API integration testing: Test HubSpot endpoints, validate OAuth, inspect webhook payloads, and debug authentication in one workspace. Import API specs, mock responses, and share test scenarios with your team.

What Is the HubSpot API?

HubSpot’s RESTful API gives you access to CRM data and marketing automation. It covers:

  • Contacts, companies, deals, tickets, and custom objects
  • Marketing emails and landing pages
  • Sales pipelines and sequences
  • Service tickets and conversations
  • Analytics and reporting
  • Workflows and automation
  • Files and assets

Key Features

Feature Description
RESTful Design Standard HTTP methods with JSON responses
OAuth 2.0 + Private Apps Flexible authentication options
Webhooks Real-time notifications for object changes
Rate Limiting Tier-based limits (100-400 requests/second)
CRM Objects Standard and custom object support
Associations Link objects together (contact-company, deal-contact)
Properties Custom fields for any object type
Search API Complex filtering and sorting

API Architecture Overview

HubSpot uses versioned REST APIs. All endpoints start with:

https://api.hubapi.com/
Enter fullscreen mode Exit fullscreen mode

API Versions Compared

Version Status Authentication Use Case
CRM API v3 Current OAuth 2.0, Private App All new integrations
Automation API v4 Current OAuth 2.0, Private App Workflow enrollment
Marketing Email API Current OAuth 2.0, Private App Email campaigns
Contacts API v1 Deprecated API Key (legacy) Migrate to v3
Companies API v1 Deprecated API Key (legacy) Migrate to v3

Important: HubSpot API key authentication is deprecated. Use OAuth 2.0 or private apps for all new and existing integrations.

Getting Started: Authentication Setup

Step 1: Create Your HubSpot Developer Account

  1. Go to the HubSpot Developer Portal
  2. Sign in or create a HubSpot account
  3. Navigate to Apps in the dashboard
  4. Click Create app

Step 2: Choose Authentication Method

HubSpot supports these authentication methods:

Method Best For Security Level
OAuth 2.0 Multi-tenant apps, public integrations High (user-scoped tokens)
Private App Internal integrations, single portal High (portal-scoped token)

Step 3: Set Up Private App (Recommended for Internal Integrations)

To use a private app:

  1. Go to Settings > Integrations > Private Apps
  2. Click Create a private app
  3. Select scopes:

    contacts
    crm.objects.companies
    crm.objects.deals
    crm.objects.tickets
    automation
    webhooks
    
  4. Generate an access token and save it securely

Example .env file:

# .env file
HUBSPOT_ACCESS_TOKEN="pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
HUBSPOT_PORTAL_ID="12345678"
Enter fullscreen mode Exit fullscreen mode

Step 4: Set Up OAuth 2.0 (For Multi-Tenant Apps)

Configure OAuth for apps needing access to multiple HubSpot portals:

const HUBSPOT_CLIENT_ID = process.env.HUBSPOT_CLIENT_ID;
const HUBSPOT_CLIENT_SECRET = process.env.HUBSPOT_CLIENT_SECRET;
const HUBSPOT_REDIRECT_URI = process.env.HUBSPOT_REDIRECT_URI;

// Build authorization URL
const getAuthUrl = (state) => {
  const params = new URLSearchParams({
    client_id: HUBSPOT_CLIENT_ID,
    redirect_uri: HUBSPOT_REDIRECT_URI,
    scope: 'crm.objects.contacts.read crm.objects.contacts.write',
    state: state,
    optional_scope: 'crm.objects.deals.read'
  });

  return `https://app.hubspot.com/oauth/authorize?${params.toString()}`;
};
Enter fullscreen mode Exit fullscreen mode

Step 5: Exchange Code for Access Token

Handle the OAuth callback to get tokens:

const exchangeCodeForToken = async (code) => {
  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: HUBSPOT_CLIENT_ID,
      client_secret: HUBSPOT_CLIENT_SECRET,
      redirect_uri: HUBSPOT_REDIRECT_URI,
      code: code
    })
  });

  const data = await response.json();

  return {
    accessToken: data.access_token,
    refreshToken: data.refresh_token,
    expiresIn: data.expires_in,
    portalId: data.hub_portal_id
  };
};

// Express route example
app.get('/oauth/callback', async (req, res) => {
  const { code, state } = req.query;

  try {
    const tokens = await exchangeCodeForToken(code);

    // Store tokens in your DB
    await db.installations.create({
      portalId: tokens.portalId,
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
      tokenExpiry: Date.now() + (tokens.expiresIn * 1000)
    });

    res.redirect('/success');
  } catch (error) {
    console.error('OAuth error:', error);
    res.status(500).send('Authentication failed');
  }
});
Enter fullscreen mode Exit fullscreen mode

Step 6: Refresh Access Token

Tokens expire after 6 hours. Use your refresh token to get new ones:

const refreshAccessToken = async (refreshToken) => {
  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: HUBSPOT_CLIENT_ID,
      client_secret: HUBSPOT_CLIENT_SECRET,
      refresh_token: refreshToken
    })
  });

  const data = await response.json();

  return {
    accessToken: data.access_token,
    refreshToken: data.refresh_token, // Always use the latest refresh token
    expiresIn: data.expires_in
  };
};

// Middleware to ensure valid token
const ensureValidToken = async (portalId) => {
  const installation = await db.installations.findByPortalId(portalId);

  // Refresh if token expires within 30 minutes
  if (installation.tokenExpiry < Date.now() + 1800000) {
    const newTokens = await refreshAccessToken(installation.refreshToken);

    await db.installations.update(installation.id, {
      accessToken: newTokens.accessToken,
      refreshToken: newTokens.refreshToken,
      tokenExpiry: Date.now() + (newTokens.expiresIn * 1000)
    });

    return newTokens.accessToken;
  }

  return installation.accessToken;
};
Enter fullscreen mode Exit fullscreen mode

Step 7: Make Authenticated API Calls

Create a reusable API client:

const HUBSPOT_BASE_URL = 'https://api.hubapi.com';

const hubspotRequest = async (endpoint, options = {}, portalId = null) => {
  const accessToken = portalId ? await ensureValidToken(portalId) : process.env.HUBSPOT_ACCESS_TOKEN;

  const response = await fetch(`${HUBSPOT_BASE_URL}${endpoint}`, {
    ...options,
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
      ...options.headers
    }
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`HubSpot API Error: ${error.message}`);
  }

  return response.json();
};

// Usage example:
const contacts = await hubspotRequest('/crm/v3/objects/contacts');
Enter fullscreen mode Exit fullscreen mode

Working with CRM Objects

Creating a Contact

To create or update a contact:

const createContact = async (contactData) => {
  const contact = {
    properties: {
      email: contactData.email,
      firstname: contactData.firstName,
      lastname: contactData.lastName,
      phone: contactData.phone,
      company: contactData.company,
      website: contactData.website,
      lifecyclestage: contactData.lifecycleStage || 'lead'
    }
  };

  const response = await hubspotRequest('/crm/v3/objects/contacts', {
    method: 'POST',
    body: JSON.stringify(contact)
  });

  return response;
};

// Usage
const contact = await createContact({
  email: 'john.doe@example.com',
  firstName: 'John',
  lastName: 'Doe',
  phone: '+1-555-0123',
  company: 'Acme Corp',
  lifecycleStage: 'customer'
});

console.log(`Contact created: ${contact.id}`);
Enter fullscreen mode Exit fullscreen mode

Contact Properties

Property Type Description
email String Primary email (unique identifier)
firstname String First name
lastname String Last name
phone String Phone number
company String Company name
website String Website URL
lifecyclestage Enum lead, marketingqualifiedlead, salesqualifiedlead, opportunity, customer, evangelist, subscriber
createdate DateTime Auto-generated
lastmodifieddate DateTime Auto-generated

Getting a Contact

Fetch a contact by ID:

const getContact = async (contactId) => {
  const response = await hubspotRequest(`/crm/v3/objects/contacts/${contactId}`);
  return response;
};

// Usage
const contact = await getContact('12345');
console.log(`${contact.properties.firstname} ${contact.properties.lastname}`);
console.log(`Email: ${contact.properties.email}`);
Enter fullscreen mode Exit fullscreen mode

Searching Contacts

Use filters to find contacts:

const searchContacts = async (searchCriteria) => {
  const response = await hubspotRequest('/crm/v3/objects/contacts/search', {
    method: 'POST',
    body: JSON.stringify({
      filterGroups: searchCriteria,
      properties: ['firstname', 'lastname', 'email', 'company'],
      limit: 100
    })
  });

  return response;
};

// Example: Find contacts at a specific company
const results = await searchContacts({
  filterGroups: [
    {
      filters: [
        {
          propertyName: 'company',
          operator: 'EQ',
          value: 'Acme Corp'
        }
      ]
    }
  ]
});

results.results.forEach(contact => {
  console.log(`${contact.properties.email}`);
});
Enter fullscreen mode Exit fullscreen mode

Search Filter Operators

Operator Description Example
EQ Equal to company EQ 'Acme'
NEQ Not equal to lifecyclestage NEQ 'subscriber'
CONTAINS_TOKEN Contains email CONTAINS_TOKEN 'gmail'
NOT_CONTAINS_TOKEN Doesn’t contain email NOT_CONTAINS_TOKEN 'test'
GT Greater than createdate GT '2026-01-01'
LT Less than createdate LT '2026-12-31'
GTE Greater or equal deal_amount GTE 10000
LTE Less or equal deal_amount LTE 50000
HAS_PROPERTY Has value phone HAS_PROPERTY
NOT_HAS_PROPERTY Missing value phone NOT_HAS_PROPERTY

Creating a Company

Create a company record:

const createCompany = async (companyData) => {
  const company = {
    properties: {
      name: companyData.name,
      domain: companyData.domain,
      industry: companyData.industry,
      numberofemployees: companyData.employees,
      annualrevenue: companyData.revenue,
      city: companyData.city,
      state: companyData.state,
      country: companyData.country
    }
  };

  const response = await hubspotRequest('/crm/v3/objects/companies', {
    method: 'POST',
    body: JSON.stringify(company)
  });

  return response;
};

// Usage
const company = await createCompany({
  name: 'Acme Corporation',
  domain: 'acme.com',
  industry: 'Technology',
  employees: 500,
  revenue: 50000000,
  city: 'San Francisco',
  state: 'CA',
  country: 'USA'
});
Enter fullscreen mode Exit fullscreen mode

Associating Objects

Link contacts to companies:

const associateContactWithCompany = async (contactId, companyId) => {
  const response = await hubspotRequest(
    `/crm/v3/objects/contacts/${contactId}/associations/companies/${companyId}`,
    {
      method: 'PUT',
      body: JSON.stringify({
        types: [
          {
            associationCategory: 'HUBSPOT_DEFINED',
            associationTypeId: 1 // Contact to Company
          }
        ]
      })
    }
  );

  return response;
};

// Usage
await associateContactWithCompany('12345', '67890');
Enter fullscreen mode Exit fullscreen mode

Association Types

Association Type ID Direction
Contact → Company 1 Contact is associated with Company
Company → Contact 1 Company has associated Contact
Deal → Contact 3 Deal is associated with Contact
Deal → Company 5 Deal is associated with Company
Ticket → Contact 16 Ticket is associated with Contact
Ticket → Company 15 Ticket is associated with Company

Creating a Deal

Create a sales opportunity:

const createDeal = async (dealData) => {
  const deal = {
    properties: {
      dealname: dealData.name,
      amount: dealData.amount.toString(),
      dealstage: dealData.stage || 'appointmentscheduled',
      pipeline: dealData.pipelineId || 'default',
      closedate: dealData.closeDate,
      dealtype: dealData.type || 'newbusiness',
      description: dealData.description
    }
  };

  const response = await hubspotRequest('/crm/v3/objects/deals', {
    method: 'POST',
    body: JSON.stringify(deal)
  });

  return response;
};

// Usage
const deal = await createDeal({
  name: 'Acme Corp - Enterprise License',
  amount: 50000,
  stage: 'qualification',
  closeDate: '2026-06-30',
  type: 'newbusiness',
  description: 'Enterprise annual subscription'
});

// Associate with company and contact
await hubspotRequest(
  `/crm/v3/objects/deals/${deal.id}/associations/companies/${companyId}`,
  { method: 'PUT', body: JSON.stringify({ types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 5 }] }) }
);

await hubspotRequest(
  `/crm/v3/objects/deals/${deal.id}/associations/contacts/${contactId}`,
  { method: 'PUT', body: JSON.stringify({ types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 3 }] }) }
);
Enter fullscreen mode Exit fullscreen mode

Deal Stages (Default Pipeline)

Stage Internal Value
Appointments Scheduled appointmentscheduled
Qualified to Buy qualifiedtobuy
Presentation Scheduled presentationscheduled
Decision Maker Bought-In decisionmakerboughtin
Contract Sent contractsent
Closed Won closedwon
Closed Lost closedlost

Webhooks

Configuring Webhooks

Set up webhooks for real-time notifications:

const createWebhook = async (webhookData) => {
  const response = await hubspotRequest('/webhooks/v3/my-app/webhooks', {
    method: 'POST',
    body: JSON.stringify({
      webhookUrl: webhookData.url,
      eventTypes: webhookData.events,
      objectType: webhookData.objectType,
      propertyName: webhookData.propertyName // Optional filter
    })
  });

  return response;
};

// Usage
const webhook = await createWebhook({
  url: 'https://myapp.com/webhooks/hubspot',
  events: [
    'contact.creation',
    'contact.propertyChange',
    'company.creation',
    'deal.creation',
    'deal.stageChange'
  ],
  objectType: 'contact'
});

console.log(`Webhook created: ${webhook.id}`);
Enter fullscreen mode Exit fullscreen mode

Webhook Event Types

Event Type Trigger
contact.creation New contact created
contact.propertyChange Contact property updated
contact.deletion Contact deleted
company.creation New company created
company.propertyChange Company property updated
deal.creation New deal created
deal.stageChange Deal stage changed
deal.propertyChange Deal property updated
ticket.creation New ticket created
ticket.propertyChange Ticket property updated

Handling Webhooks

Example Express handler with signature verification:

const express = require('express');
const crypto = require('crypto');
const app = express();

app.post('/webhooks/hubspot', express.json(), async (req, res) => {
  const signature = req.headers['x-hubspot-signature'];
  const payload = JSON.stringify(req.body);

  // Verify webhook signature
  const isValid = verifyWebhookSignature(payload, signature, process.env.HUBSPOT_CLIENT_SECRET);

  if (!isValid) {
    console.error('Invalid webhook signature');
    return res.status(401).send('Unauthorized');
  }

  const events = req.body;

  for (const event of events) {
    console.log(`Event: ${event.eventType}`);
    console.log(`Object: ${event.objectType} - ${event.objectId}`);
    console.log(`Property: ${event.propertyName}`);
    console.log(`Value: ${event.propertyValue}`);

    // Route to appropriate handler
    switch (event.eventType) {
      case 'contact.creation':
        await handleContactCreation(event);
        break;
      case 'contact.propertyChange':
        await handleContactUpdate(event);
        break;
      case 'deal.stageChange':
        await handleDealStageChange(event);
        break;
    }
  }

  res.status(200).send('OK');
});

function verifyWebhookSignature(payload, signature, clientSecret) {
  const expectedSignature = crypto
    .createHmac('sha256', clientSecret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expectedSignature, 'hex')
  );
}
Enter fullscreen mode Exit fullscreen mode

Rate Limiting

Understanding Rate Limits

HubSpot enforces rate limits by subscription tier:

Tier Requests/Second Requests/Day
Free/Starter 100 100,000
Professional 200 500,000
Enterprise 400 1,000,000

If you exceed the limit, you’ll get HTTP 429 (Too Many Requests).

Rate Limit Headers

Header Description
X-HubSpot-RateLimit-Second-Limit Max requests per second
X-HubSpot-RateLimit-Second-Remaining Remaining requests this second
X-HubSpot-RateLimit-Second-Reset Seconds until second limit resets
X-HubSpot-RateLimit-Daily-Limit Max requests per day
X-HubSpot-RateLimit-Daily-Remaining Remaining requests today
X-HubSpot-RateLimit-Daily-Reset Seconds until daily limit resets

Implementing Rate Limit Handling

Basic retry logic and queueing:

const makeRateLimitedRequest = async (endpoint, options = {}, maxRetries = 3) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await hubspotRequest(endpoint, options);

      // Log rate limit info
      const remaining = response.headers.get('X-HubSpot-RateLimit-Second-Remaining');
      if (remaining < 10) {
        console.warn(`Low rate limit remaining: ${remaining}`);
      }

      return response;
    } catch (error) {
      if (error.message.includes('429') && attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000;
        console.log(`Rate limited. Retrying in ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      } else {
        throw error;
      }
    }
  }
};

// Rate limiter class
class HubSpotRateLimiter {
  constructor(requestsPerSecond = 90) { // Stay under limit
    this.queue = [];
    this.interval = 1000 / requestsPerSecond;
    this.processing = false;
  }

  async add(requestFn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ requestFn, resolve, reject });
      this.process();
    });
  }

  async process() {
    if (this.processing || this.queue.length === 0) return;
    this.processing = true;

    while (this.queue.length > 0) {
      const { requestFn, resolve, reject } = this.queue.shift();
      try {
        const result = await requestFn();
        resolve(result);
      } catch (error) {
        reject(error);
      }
      if (this.queue.length > 0) {
        await new Promise(r => setTimeout(r, this.interval));
      }
    }
    this.processing = false;
  }
}
Enter fullscreen mode Exit fullscreen mode

Production Deployment Checklist

Before going live, ensure you:

  • [ ] Use private app or OAuth 2.0 authentication
  • [ ] Store tokens securely (encrypted DB)
  • [ ] Implement automatic token refresh
  • [ ] Set up rate limiting and request queuing
  • [ ] Configure webhook endpoints with HTTPS
  • [ ] Implement comprehensive error handling
  • [ ] Add logging for all API calls
  • [ ] Monitor rate limit usage
  • [ ] Create a runbook for common issues

Real-World Use Cases

CRM Synchronization

A SaaS company syncs customer data:

  • Challenge: Manual data entry between app and HubSpot
  • Solution: Real-time sync via webhooks and API
  • Result: Zero manual entry, 100% data accuracy

Lead Routing

A marketing agency automates lead distribution:

  • Challenge: Slow lead response times
  • Solution: Webhook-triggered routing to sales reps
  • Result: 5-minute response time, 40% conversion increase

Conclusion

The HubSpot API gives you robust CRM and marketing automation. To implement:

  • Use OAuth 2.0 for multi-tenant apps, private apps for internal integrations
  • Respect rate limits (100–400 requests/second by tier)
  • Use webhooks for real-time sync
  • Leverage associations and custom properties for CRM objects
  • Use Apidog to streamline API testing and collaboration

FAQ Section

How do I authenticate with HubSpot API?

Use OAuth 2.0 for multi-tenant apps, or private apps for single-portal integrations. API key authentication is deprecated.

What are HubSpot rate limits?

100–400 requests/sec depending on tier, daily limits from 100K to 1M.

How do I create a contact in HubSpot?

POST to /crm/v3/objects/contacts with properties: email, firstname, lastname, and any custom fields.

Can I create custom properties?

Yes. Use the Properties API for custom fields on any object.

How do webhooks work in HubSpot?

Configure webhooks in your app settings. HubSpot sends POSTs to your endpoint when events occur.

Top comments (0)