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 los desarrolladores que crean integraciones de CRM, automatización de marketing o herramientas de ventas, la integración con la API de HubSpot no es opcional, es esencial para llegar a más de 7 millones de usuarios.
Esta es la realidad: las empresas pierden entre 15 y 20 horas semanales en la introducción manual de datos entre sistemas. Una sólida integración con la API de HubSpot automatiza la sincronización de contactos, las actualizaciones de negocios, los flujos de trabajo de marketing y los informes en todas las plataformas.
¿Qué es la API de HubSpot?
HubSpot proporciona una API RESTful para acceder a los datos de CRM y a las funciones de automatización de marketing. La API gestiona:
- 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. Migre todas las integraciones inmediatamente.
Primeros Pasos: Configuración de Autenticación
Paso 1: Cree su Cuenta de Desarrollador de HubSpot
Antes de acceder a la API:
- Visite el Portal de Desarrolladores de HubSpot
- Inicie sesión con su cuenta de HubSpot (o cree una)
- Navegue a Apps en el panel de desarrolladores
- Haga clic en Crear app
Paso 2: Elija 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: Configurar Aplicación Privada (Recomendado para Integraciones Internas)
Cree una aplicación privada para acceso de portal único:
- Vaya a Configuración > Integraciones > Aplicaciones Privadas
- Haga clic en Crear una aplicación privada
- Configure los ámbitos (scopes):
contacts
crm.objects.companies
crm.objects.deals
crm.objects.tickets
automation
webhooks
- Genere un token de acceso
- Cópielo y guárdelo de forma segura
# archivo .env
HUBSPOT_ACCESS_TOKEN="pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
HUBSPOT_PORTAL_ID="12345678"
Paso 4: Configurar OAuth 2.0 (Para Aplicaciones Multi-tenant)
Configure OAuth para acceso a múltiples portales:
- Vaya a Apps > Crear app
- Configure los ajustes de autenticación:
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: Intercambiar Código por Token de Acceso
Manejar 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: Refrescar Token de Acceso
Los tokens de acceso expiran después de 6 horas:
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: Realizar Llamadas a la API Autenticadas
Crear un cliente de API reutilizable:
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
Crear o actualizar 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
Obtener 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
Buscar con filtros:
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 - Encontrar contactos en una 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
Crear 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
Vincular 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
Crear oportunidad de venta:
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
Configure webhooks para 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: filtrar por cambio de propiedad
})
});
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
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 basados en el nivel de suscripción:
| Nivel | Solicitudes/Segundo | Solicitudes/Día |
|---|---|---|
| Gratis/Starter | 100 | 100.000 |
| Profesional | 200 | 500.000 |
| Empresarial | 400 | 1.000.000 |
Exceder los límites resulta en respuestas 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
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 salir en vivo:
- [ ] Utilice autenticación de aplicación privada o OAuth 2.0
- [ ] Almacene los tokens de forma segura (base de datos cifrada)
- [ ] Implemente la actualización automática de tokens
- [ ] Configure la limitación de tasa y el encolamiento de solicitudes
- [ ] Configure el endpoint del webhook con HTTPS
- [ ] Implemente un manejo de errores exhaustivo
- [ ] Agregue registro para todas las llamadas a la API
- [ ] Monitoree el uso del límite de tasa
- [ ] Cree un manual de procedimientos para problemas comunes
Casos de Uso en el Mundo Real
Sincronización de CRM
Una empresa SaaS sincroniza los datos de los clientes:
- Desafío: Entrada manual de datos entre la aplicación y HubSpot
- Solución: Sincronización en tiempo real a través de webhooks y API
- Resultado: Cero entrada manual, 100% de precisión de datos
Enrutamiento de Leads
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 minutos, aumento del 40% en la conversión
Conclusión
La API de HubSpot proporciona capacidades integrales de CRM y automatización de marketing. Puntos clave:
- Utilice 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 la 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?
Utilice 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?
Realice una solicitud POST a /crm/v3/objects/contacts con propiedades que incluyan email, firstname, lastname y cualquier campo personalizado.
¿Puedo crear propiedades personalizadas?
Sí, utilice la API de Propiedades para crear campos personalizados para cualquier tipo de objeto.
¿Cómo funcionan los webhooks en HubSpot?
Configure los webhooks en la configuración de su aplicación. HubSpot envía solicitudes POST a su endpoint cuando ocurren eventos específicos.
