How to Use HubSpot API in 2026

Master HubSpot API integration with this complete guide. Learn OAuth 2.0, CRM objects, webhooks, rate limiting, and production integration strategies.

Ashley Innocent

Ashley Innocent

25 March 2026

How to Use HubSpot API in 2026

TL;DR

The HubSpot API enables developers to integrate with CRM, marketing, sales, and service hubs programmatically. It uses OAuth 2.0 and private app authentication, RESTful endpoints for contacts, companies, deals, tickets, and more, with rate limits based on subscription tier. This guide covers authentication setup, core endpoints, webhooks, and production integration strategies.

Introduction

HubSpot manages over 194,000 customer accounts and billions of CRM records. For developers building CRM integrations, marketing automation, or sales tools, HubSpot API integration isn’t optional—it’s essential for reaching 7 million+ users.

Here’s the reality: businesses lose 15-20 hours weekly on manual data entry between systems. A solid HubSpot API integration automates contact synchronization, deal updates, marketing workflows, and reporting across platforms.

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

What Is the HubSpot API?

HubSpot provides a RESTful API for accessing CRM data and marketing automation features. The API handles:

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:

https://api.hubapi.com/

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 deprecated API key authentication in favor of OAuth 2.0 and private apps. Migrate all integrations immediately.

Getting Started: Authentication Setup

Step 1: Create Your HubSpot Developer Account

Before accessing the API:

  1. Visit the HubSpot Developer Portal
  2. Sign in with your HubSpot account (or create one)
  3. Navigate to Apps in the developer dashboard
  4. Click Create app

Step 2: Choose Authentication Method

HubSpot supports two 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)

Create a private app for single-portal access:

  1. Go to Settings > Integrations > Private Apps
  2. Click Create a private app
  3. Configure scopes:
contacts
crm.objects.companies
crm.objects.deals
crm.objects.tickets
automation
webhooks
  1. Generate access token
  2. Copy and store securely
# .env file
HUBSPOT_ACCESS_TOKEN="pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
HUBSPOT_PORTAL_ID="12345678"

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

Configure OAuth for multi-portal access:

  1. Go to Apps > Create app
  2. Configure auth settings:
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()}`;
};

Step 5: Exchange Code for Access Token

Handle OAuth callback:

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
  };
};

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

  try {
    const tokens = await exchangeCodeForToken(code);

    // Store tokens in database
    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');
  }
});

Step 6: Refresh Access Token

Access tokens expire after 6 hours:

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 save new refresh token
    expiresIn: data.expires_in
  };
};

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

  // Refresh if 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;
};

Step 7: Make Authenticated API Calls

Create 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
const contacts = await hubspotRequest('/crm/v3/objects/contacts');

Working with CRM Objects

Creating a Contact

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}`);

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 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}`);

Searching Contacts

Search with filters:

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;
};

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

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

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 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'
});

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');

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 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 }] }) }
);

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 by property change
    })
  });

  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}`);

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

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')
  );
}

Rate Limiting

Understanding Rate Limits

HubSpot enforces rate limits based on subscription tier:

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

Exceeding limits results in HTTP 429 (Too Many Requests) responses.

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

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;
  }
}

Production Deployment Checklist

Before going live:


Real-World Use Cases

CRM Synchronization

A SaaS company syncs customer data:

Lead Routing

A marketing agency automates lead distribution:

Conclusion

The HubSpot API provides comprehensive CRM and marketing automation capabilities. Key takeaways:

button

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?

Rate limits range from 100 requests/second (Free) to 400 requests/second (Enterprise), with daily limits from 100K to 1M requests.

How do I create a contact in HubSpot?

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

Can I create custom properties?

Yes, use the Properties API to create custom fields for any object type.

How do webhooks work in HubSpot?

Configure webhooks in your app settings. HubSpot sends POST requests to your endpoint when specified events occur.

Explore more

How to Use Make (Integromat) API ?

How to Use Make (Integromat) API ?

Master Make (Integromat) API integration with this complete guide. Learn scenario management, webhooks, execution monitoring, and production automation strategies.

25 March 2026

How Do You Build a HIPAA-Compliant API in 2026 for Secure Healthcare Applications?

How Do You Build a HIPAA-Compliant API in 2026 for Secure Healthcare Applications?

Complete guide to HIPAA-compliant API development. Learn PHI handling, encryption, access controls, audit logging, and compliance verification for healthcare applications.

25 March 2026

How to Use AWS Lambda API for Serverless in 2026

How to Use AWS Lambda API for Serverless in 2026

Master AWS Lambda API integration with this complete guide. Learn IAM authentication, function deployment, invocation patterns, event sources, and serverless architecture.

25 March 2026

Practice API Design-first in Apidog

Discover an easier way to build and use APIs