TL;DR
La API de HubSpot permite a los desarrolladores integrarse programáticamente con los hubs de CRM, marketing, ventas y servicio. Utiliza autenticación OAuth 2.0 y de aplicaciones privadas, endpoints RESTful para contactos, empresas, negocios, tickets y más, con límites de tasa basados en el nivel de suscripción. Esta guía cubre la configuración de autenticación, los endpoints principales, los webhooks y las estrategias de integración en producción.
Introducción
HubSpot gestiona más de 194.000 cuentas de clientes y miles de millones de registros de CRM. Para desarrolladores que crean integraciones de CRM, automatización de marketing o herramientas de ventas, la integración con la API de HubSpot es esencial para llegar a más de 7 millones de usuarios.
Las empresas pierden entre 15 y 20 horas semanales en la introducción manual de datos entre sistemas. Una integración sólida con la API de HubSpot automatiza la sincronización de contactos, actualizaciones de negocios, flujos de trabajo de marketing y reportes entre plataformas.
💡 Apidog simplifica las pruebas de integración de API. Prueba tus endpoints de HubSpot, valida flujos de OAuth, inspecciona cargas útiles de webhook y depura problemas de autenticación en un solo espacio de trabajo. Importa especificaciones de API, simula respuestas y comparte escenarios de prueba con tu equipo.
¿Qué es la API de HubSpot?
HubSpot proporciona una API RESTful para acceder a datos de CRM y funciones de automatización de marketing. Con esta API puedes gestionar:
- Contactos, empresas, negocios, tickets y objetos personalizados
- Correos electrónicos de marketing y páginas de destino
- Embudo de ventas y secuencias
- Tickets de servicio y conversaciones
- Análisis e informes
- Flujos de trabajo y automatización
- Archivos y activos
Características Clave
| Característica | Descripción |
|---|---|
| Diseño RESTful | Métodos HTTP estándar con respuestas JSON |
| OAuth 2.0 + Apps Privadas | Opciones de autenticación flexibles |
| Webhooks | Notificaciones en tiempo real para cambios de objetos |
| Limitación de Tasa | Límites basados en niveles (100-400 solicitudes/segundo) |
| Objetos CRM | Soporte para objetos estándar y personalizados |
| Asociaciones | Vincular objetos (contacto-empresa, negocio-contacto) |
| Propiedades | Campos personalizados para cualquier tipo de objeto |
| API de Búsqueda | Filtrado y clasificación complejos |
Descripción General de la Arquitectura de la API
HubSpot utiliza APIs REST versionadas:
https://api.hubapi.com/
Versiones de la API Comparadas
| Versión | Estado | Autenticación | Caso de Uso |
|---|---|---|---|
| CRM API v3 | Actual | OAuth 2.0, App Privada | Todas las nuevas integraciones |
| Automation API v4 | Actual | OAuth 2.0, App Privada | Inscripción en flujos de trabajo |
| Marketing Email API | Actual | OAuth 2.0, App Privada | Campañas de email |
| Contacts API v1 | Obsoleta | Clave API (legado) | Migrar a v3 |
| Companies API v1 | Obsoleta | Clave API (legado) | Migrar a v3 |
Importante: HubSpot ha dejado de usar la autenticación con clave API en favor de OAuth 2.0 y las aplicaciones privadas. Migra todas tus integraciones inmediatamente.
Primeros Pasos: Configuración de Autenticación
Paso 1: Crea tu Cuenta de Desarrollador de HubSpot
- Visita el Portal de Desarrolladores de HubSpot
- Inicia sesión con tu cuenta de HubSpot (o crea una)
- Ve a Apps en el panel de desarrolladores
- Haz clic en Crear app
Paso 2: Elige el Método de Autenticación
HubSpot soporta dos métodos de autenticación:
| Método | Mejor para | Nivel de Seguridad |
|---|---|---|
| OAuth 2.0 | Aplicaciones multi-tenant, integraciones públicas | Alto (tokens con alcance de usuario) |
| App Privada | Integraciones internas, portal único | Alto (token con alcance de portal) |
Paso 3: Configura una Aplicación Privada (Integraciones Internas)
- Ve a Configuración > Integraciones > Aplicaciones Privadas
- Haz clic en Crear una aplicación privada
- Configura los ámbitos (scopes):
contacts
crm.objects.companies
crm.objects.deals
crm.objects.tickets
automation
webhooks
- Genera un token de acceso y guárdalo de forma segura.
# archivo .env
HUBSPOT_ACCESS_TOKEN="pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
HUBSPOT_PORTAL_ID="12345678"
Paso 4: Configura OAuth 2.0 (Aplicaciones Multi-tenant)
Configura OAuth para acceso a múltiples portales:
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;
// Construir URL de autorización
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()}`;
};
Paso 5: Intercambia el Código por un Token de Acceso
Maneja la devolución de llamada de OAuth:
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
};
};
// Manejar la devolución de llamada
app.get('/oauth/callback', async (req, res) => {
const { code, state } = req.query;
try {
const tokens = await exchangeCodeForToken(code);
// Almacenar tokens en la base de datos
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('Error de OAuth:', error);
res.status(500).send('Autenticación fallida');
}
});
Paso 6: Refresca el Token de Acceso
Los tokens de acceso expiran después de 6 horas. Implementa refresco automático:
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, // Siempre guarda el nuevo refresh token
expiresIn: data.expires_in
};
};
// Middleware para asegurar un token válido
const ensureValidToken = async (portalId) => {
const installation = await db.installations.findByPortalId(portalId);
// Refrescar si expira dentro de 30 minutos
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;
};
Paso 7: Realiza Llamadas a la API Autenticadas
Crea un cliente reutilizable para tus requests:
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(`Error de la API de HubSpot: ${error.message}`);
}
return response.json();
};
// Uso
const contacts = await hubspotRequest('/crm/v3/objects/contacts');
Trabajando con Objetos CRM
Creando un Contacto
Crea o actualiza un contacto:
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;
};
// Uso
const contact = await createContact({
email: 'john.doe@example.com',
firstName: 'John',
lastName: 'Doe',
phone: '+1-555-0123',
company: 'Acme Corp',
lifecycleStage: 'customer'
});
console.log(`Contacto creado: ${contact.id}`);
Propiedades del Contacto
| Propiedad | Tipo | Descripción |
|---|---|---|
email |
Cadena | Email principal (identificador único) |
firstname |
Cadena | Nombre |
lastname |
Cadena | Apellido |
phone |
Cadena | Número de teléfono |
company |
Cadena | Nombre de la empresa |
website |
Cadena | URL del sitio web |
lifecyclestage |
Enumeración | lead, marketingqualifiedlead, salesqualifiedlead, opportunity, customer, evangelist, subscriber |
createdate |
Fecha y Hora | Generado automáticamente |
lastmodifieddate |
Fecha y Hora | Generado automáticamente |
Obteniendo un Contacto
Recupera un contacto por ID:
const getContact = async (contactId) => {
const response = await hubspotRequest(`/crm/v3/objects/contacts/${contactId}`);
return response;
};
// Uso
const contact = await getContact('12345');
console.log(`${contact.properties.firstname} ${contact.properties.lastname}`);
console.log(`Email: ${contact.properties.email}`);
Buscando Contactos
Filtra contactos por propiedades:
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;
};
// Uso - Buscar contactos de empresa específica
const results = await searchContacts({
filterGroups: [
{
filters: [
{
propertyName: 'company',
operator: 'EQ',
value: 'Acme Corp'
}
]
}
]
});
results.results.forEach(contact => {
console.log(`${contact.properties.email}`);
});
Operadores de Filtro de Búsqueda
| Operador | Descripción | Ejemplo |
|---|---|---|
EQ |
Igual a | company EQ 'Acme' |
NEQ |
No igual a | lifecyclestage NEQ 'subscriber' |
CONTAINS_TOKEN |
Contiene | email CONTAINS_TOKEN 'gmail' |
NOT_CONTAINS_TOKEN |
No contiene | email NOT_CONTAINS_TOKEN 'test' |
GT |
Mayor que | createdate GT '2026-01-01' |
LT |
Menor que | createdate LT '2026-12-31' |
GTE |
Mayor o igual | deal_amount GTE 10000 |
LTE |
Menor o igual | deal_amount LTE 50000 |
HAS_PROPERTY |
Tiene valor | phone HAS_PROPERTY |
NOT_HAS_PROPERTY |
Valor faltante | phone NOT_HAS_PROPERTY |
Creando una Empresa
Crea un registro de empresa:
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;
};
// Uso
const company = await createCompany({
name: 'Acme Corporation',
domain: 'acme.com',
industry: 'Technology',
employees: 500,
revenue: 50000000,
city: 'San Francisco',
state: 'CA',
country: 'USA'
});
Asociando Objetos
Vincula contactos a empresas:
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 // Contacto a Empresa
}
]
})
}
);
return response;
};
// Uso
await associateContactWithCompany('12345', '67890');
Tipos de Asociación
| Asociación | ID de Tipo | Dirección |
|---|---|---|
| Contacto → Empresa | 1 | El contacto está asociado con la empresa |
| Empresa → Contacto | 1 | La empresa tiene un contacto asociado |
| Negocio → Contacto | 3 | El negocio está asociado con el contacto |
| Negocio → Empresa | 5 | El negocio está asociado con la empresa |
| Ticket → Contacto | 16 | El ticket está asociado con el contacto |
| Ticket → Empresa | 15 | El ticket está asociado con la empresa |
Creando un Negocio
Crea una oportunidad de venta (deal):
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;
};
// Uso
const deal = await createDeal({
name: 'Acme Corp - Licencia Empresarial',
amount: 50000,
stage: 'qualification',
closeDate: '2026-06-30',
type: 'newbusiness',
description: 'Suscripción anual empresarial'
});
// Asociar con empresa y contacto
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 }] }) }
);
Etapas de Negocio (Embudo por Defecto)
| Etapa | Valor Interno |
|---|---|
| Citas Programadas | appointmentscheduled |
| Calificado para Comprar | qualifiedtobuy |
| Presentación Programada | presentationscheduled |
| Responsable de la Toma de Decisiones Comprometido | decisionmakerboughtin |
| Contrato Enviado | contractsent |
| Cerrado Ganado | closedwon |
| Cerrado Perdido | closedlost |
Webhooks
Configurando Webhooks
Configura webhooks para recibir notificaciones en tiempo real:
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 // Opcional
})
});
return response;
};
// Uso
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 creado: ${webhook.id}`);
Tipos de Eventos de Webhook
| Tipo de Evento | Disparador |
|---|---|
contact.creation |
Nuevo contacto creado |
contact.propertyChange |
Propiedad de contacto actualizada |
contact.deletion |
Contacto eliminado |
company.creation |
Nueva empresa creada |
company.propertyChange |
Propiedad de empresa actualizada |
deal.creation |
Nuevo negocio creado |
deal.stageChange |
Etapa de negocio cambiada |
deal.propertyChange |
Propiedad de negocio actualizada |
ticket.creation |
Nuevo ticket creado |
ticket.propertyChange |
Propiedad de ticket actualizada |
Manejo de Webhooks
Implementa la verificación de firma y manejo básico de eventos:
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);
// Verificar firma del webhook
const isValid = verifyWebhookSignature(payload, signature, process.env.HUBSPOT_CLIENT_SECRET);
if (!isValid) {
console.error('Firma de webhook inválida');
return res.status(401).send('No autorizado');
}
const events = req.body;
for (const event of events) {
console.log(`Evento: ${event.eventType}`);
console.log(`Objeto: ${event.objectType} - ${event.objectId}`);
console.log(`Propiedad: ${event.propertyName}`);
console.log(`Valor: ${event.propertyValue}`);
// Dirigir al manejador apropiado
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')
);
}
Limitación de Tasa
Entendiendo los Límites de Tasa
HubSpot aplica límites de tasa según nivel de suscripción:
| Nivel | Solicitudes/Segundo | Solicitudes/Día |
|---|---|---|
| Gratis/Starter | 100 | 100.000 |
| Profesional | 200 | 500.000 |
| Empresarial | 400 | 1.000.000 |
Si excedes los límites, recibirás HTTP 429 (Demasiadas Solicitudes).
Encabezados de Límite de Tasa
| Encabezado | Descripción |
|---|---|
X-HubSpot-RateLimit-Second-Limit |
Máximo de solicitudes por segundo |
X-HubSpot-RateLimit-Second-Remaining |
Solicitudes restantes este segundo |
X-HubSpot-RateLimit-Second-Reset |
Segundos hasta que se restablezca el límite de segundo |
X-HubSpot-RateLimit-Daily-Limit |
Máximo de solicitudes por día |
X-HubSpot-RateLimit-Daily-Remaining |
Solicitudes restantes hoy |
X-HubSpot-RateLimit-Daily-Reset |
Segundos hasta que se restablezca el límite diario |
Implementando el Manejo de Límites de Tasa
Implementa gestión y reintentos:
const makeRateLimitedRequest = async (endpoint, options = {}, maxRetries = 3) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await hubspotRequest(endpoint, options);
// Registrar información de límite de tasa
const remaining = response.headers.get('X-HubSpot-RateLimit-Second-Remaining');
if (remaining < 10) {
console.warn(`Límite de tasa bajo restante: ${remaining}`);
}
return response;
} catch (error) {
if (error.message.includes('429') && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
console.log(`Límite de tasa alcanzado. Reintentando en ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
};
// Clase de limitador de tasa
class HubSpotRateLimiter {
constructor(requestsPerSecond = 90) { // Mantenerse por debajo del límite
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;
}
}
Lista de Verificación para Despliegue en Producción
Antes de lanzar tu integración:
- [ ] Usa autenticación de aplicación privada o OAuth 2.0
- [ ] Almacena los tokens de forma segura (base de datos cifrada)
- [ ] Implementa la actualización automática de tokens
- [ ] Configura limitación de tasa y encolamiento de solicitudes
- [ ] Usa HTTPS en tu endpoint de webhook
- [ ] Maneja errores de forma exhaustiva
- [ ] Registra todas las llamadas a la API
- [ ] Monitorea el uso del límite de tasa
- [ ] Documenta procedimientos para problemas comunes
Casos de Uso en el Mundo Real
Sincronización de CRM
Escenario: Una empresa SaaS sincroniza los datos de clientes
- Desafío: Entrada manual de datos entre la app y HubSpot
- Solución: Sincronización en tiempo real usando webhooks y API
- Resultado: 0 entrada manual, 100% precisión de datos
Enrutamiento de Leads
Escenario: Una agencia de marketing automatiza la distribución de leads
- Desafío: Tiempos de respuesta lentos a los leads
- Solución: Enrutamiento a representantes de ventas activado por webhook
- Resultado: Tiempo de respuesta de 5 min, +40% conversión
Conclusión
La API de HubSpot proporciona capacidades integrales de CRM y automatización de marketing. Resumen de prácticas clave:
- Usa OAuth 2.0 para aplicaciones multi-tenant, aplicaciones privadas para integraciones internas
- Los límites de tasa varían según el nivel (100-400 solicitudes/segundo)
- Los webhooks permiten sincronización en tiempo real
- Los objetos de CRM admiten asociaciones y propiedades personalizadas
- Apidog agiliza las pruebas de API y la colaboración en equipo
Sección de Preguntas Frecuentes
¿Cómo me autentico con la API de HubSpot?
Utiliza OAuth 2.0 para aplicaciones multi-tenant o aplicaciones privadas para integraciones de portal único. La autenticación con clave API está obsoleta.
¿Cuáles son los límites de tasa de HubSpot?
Los límites de tasa varían de 100 solicitudes/segundo (Gratis) a 400 solicitudes/segundo (Empresarial), con límites diarios de 100K a 1M de solicitudes.
¿Cómo creo un contacto en HubSpot?
Realiza una solicitud POST a /crm/v3/objects/contacts con propiedades que incluyan email, firstname, lastname y cualquier campo personalizado.
¿Puedo crear propiedades personalizadas?
Sí, utiliza la API de Propiedades para crear campos personalizados para cualquier tipo de objeto.
¿Cómo funcionan los webhooks en HubSpot?
Configura los webhooks en la configuración de tu aplicación. HubSpot envía solicitudes POST a tu endpoint cuando ocurren eventos específicos.
Top comments (0)