TL;DR
Die HubSpot API ermöglicht die programmatische Integration mit CRM-, Marketing-, Vertriebs- und Service-Hubs. Sie nutzt OAuth 2.0 und private Apps zur Authentifizierung sowie RESTful-Endpunkte für Kontakte, Unternehmen, Deals und Tickets. Die Ratenbegrenzung basiert auf der Abonnementstufe. In diesem Leitfaden lernst du, wie du die Authentifizierung einrichtest, Kern-Endpunkte ansteuerst, Webhooks verwendest und Integrationen produktionsreif umsetzt.
Einführung
HubSpot verwaltet über 194.000 Kundenkonten und Milliarden von CRM-Datensätzen. Für Entwickler, die CRM-Integrationen, Marketing-Automatisierung oder Vertriebstools bauen, ist die HubSpot API-Integration unverzichtbar, um über 7 Millionen Nutzer zu erreichen.
Unternehmen verlieren jede Woche 15-20 Stunden durch manuelle Dateneingabe zwischen Systemen. Mit einer soliden HubSpot API-Integration automatisierst du Kontaktsynchronisation, Deal-Updates, Marketing-Workflows und Berichte über verschiedene Plattformen hinweg.
💡 Apidog vereinfacht das Testen von API-Integrationen: Teste HubSpot-Endpunkte, validiere OAuth-Flows, inspiziere Webhook-Nutzdaten und debugge Authentifizierungsprobleme zentral. Importiere API-Spezifikationen, mocke Antworten und teile Testszenarien im Team.
Was ist die HubSpot API?
HubSpot bietet eine RESTful API für den Zugriff auf CRM-Daten und Marketing-Funktionen. Die API unterstützt:
- Kontakte, Unternehmen, Deals, Tickets, benutzerdefinierte Objekte
- Marketing-E-Mails, Landing Pages
- Vertriebs-Pipelines, Sequenzen
- Service-Tickets, Konversationen
- Analysen, Berichte
- Workflows, Automatisierung
- Dateien, Assets
Hauptfunktionen
| Funktion | Beschreibung |
|---|---|
| RESTful Design | Standard-HTTP-Methoden mit JSON-Antworten |
| OAuth 2.0 + Private Apps | Flexible Authentifizierungsoptionen |
| Webhooks | Echtzeit-Benachrichtigungen für Objektänderungen |
| Ratenbegrenzung | Tierbasierte Limits (100-400 Anfragen/Sekunde) |
| CRM-Objekte | Unterstützung für Standard- und benutzerdefinierte Objekte |
| Verknüpfungen | Objekte miteinander verknüpfen (Kontakt-Unternehmen, Deal-Kontakt) |
| Eigenschaften | Benutzerdefinierte Felder für jeden Objekttyp |
| Such-API | Komplexe Filterung und Sortierung |
API-Architekturübersicht
HubSpot verwendet versionierte REST-APIs:
https://api.hubapi.com/
API-Versionen im Vergleich
| Version | Status | Authentifizierung | Anwendungsfall |
|---|---|---|---|
| CRM API v3 | Aktuell | OAuth 2.0, Private App | Alle neuen Integrationen |
| Automatisierungs-API v4 | Aktuell | OAuth 2.0, Private App | Workflow-Einschreibung |
| Marketing-E-Mail-API | Aktuell | OAuth 2.0, Private App | E-Mail-Kampagnen |
| Kontakte-API v1 | Veraltet | API-Schlüssel (veraltet) | Zu v3 migrieren |
| Unternehmen-API v1 | Veraltet | API-Schlüssel (veraltet) | Zu v3 migrieren |
Wichtig: HubSpot hat API-Schlüssel-Authentifizierung eingestellt. Nutze ab sofort nur OAuth 2.0 oder private Apps.
Erste Schritte: Authentifizierungseinrichtung
Schritt 1: HubSpot-Entwicklerkonto erstellen
- Besuche das HubSpot Developer Portal
- Mit HubSpot-Konto anmelden oder neues Konto erstellen
- Zu Apps im Dashboard navigieren
- App erstellen klicken
Schritt 2: Authentifizierungsmethode wählen
HubSpot unterstützt zwei Methoden:
| Methode | Am besten geeignet für | Sicherheitsstufe |
|---|---|---|
| OAuth 2.0 | Multi-Tenant-Apps, öffentliche Integrationen | Hoch (benutzerbezogene Tokens) |
| Private App | Interne Integrationen, einzelnes Portal | Hoch (portalbezogener Token) |
Schritt 3: Private App einrichten (empfohlen für interne Integrationen)
- In HubSpot: Einstellungen > Integrationen > Private Apps
- Eine private App erstellen
-
Benötigte Bereiche konfigurieren, z.B.:
contacts crm.objects.companies crm.objects.deals crm.objects.tickets automation webhooks Zugriffs-Token generieren und sicher speichern
.env Beispiel:
HUBSPOT_ACCESS_TOKEN="pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
HUBSPOT_PORTAL_ID="12345678"
Schritt 4: OAuth 2.0 einrichten (für Multi-Tenant-Apps)
- Unter Apps > App erstellen
- Authentifizierung konfigurieren:
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;
// Authorisierungs-URL bauen
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()}`;
};
Schritt 5: Code gegen Zugriffs-Token austauschen
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
};
};
// Callback-Handler
app.get('/oauth/callback', async (req, res) => {
const { code, state } = req.query;
try {
const tokens = await exchangeCodeForToken(code);
// Tokens speichern
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');
}
});
Schritt 6: Zugriffs-Token aktualisieren
Tokens verfallen nach 6 Stunden. Implementiere Refresh:
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,
expiresIn: data.expires_in
};
};
// Middleware zur Token-Prüfung
const ensureValidToken = async (portalId) => {
const installation = await db.installations.findByPortalId(portalId);
if (installation.tokenExpiry < Date.now() + 1800000) { // 30 Min Puffer
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;
};
Schritt 7: Authentifizierte API-Aufrufe tätigen
Erstelle einen wiederverwendbaren 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();
};
// Beispielnutzung
const contacts = await hubspotRequest('/crm/v3/objects/contacts');
Arbeiten mit CRM-Objekten
Einen Kontakt erstellen
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;
};
// Nutzung
const contact = await createContact({
email: 'john.doe@example.com',
firstName: 'John',
lastName: 'Doe',
phone: '+1-555-0123',
company: 'Acme Corp',
lifecycleStage: 'customer'
});
console.log(`Kontakt erstellt: ${contact.id}`);
Kontakteigenschaften
| Eigenschaft | Typ | Beschreibung |
|---|---|---|
email |
String | Primäre E-Mail (eindeutiger Identifikator) |
firstname |
String | Vorname |
lastname |
String | Nachname |
phone |
String | Telefonnummer |
company |
String | Firmenname |
website |
String | Website-URL |
lifecyclestage |
Enum | Lead, Marketing Qualified Lead, Sales Qualified Lead, Opportunity, Kunde, Evangelist, Abonnent |
createdate |
DateTime | Automatisch generiert |
lastmodifieddate |
DateTime | Automatisch generiert |
Einen Kontakt abrufen
const getContact = async (contactId) => {
const response = await hubspotRequest(`/crm/v3/objects/contacts/${contactId}`);
return response;
};
// Beispiel
const contact = await getContact('12345');
console.log(`${contact.properties.firstname} ${contact.properties.lastname}`);
console.log(`E-Mail: ${contact.properties.email}`);
Kontakte suchen
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;
};
// Suche nach Firma
const results = await searchContacts({
filterGroups: [
{
filters: [
{
propertyName: 'company',
operator: 'EQ',
value: 'Acme Corp'
}
]
}
]
});
results.results.forEach(contact => {
console.log(`${contact.properties.email}`);
});
Suchfilter-Operatoren
| Operator | Beschreibung | Beispiel |
|---|---|---|
EQ |
Gleich | company EQ 'Acme' |
NEQ |
Nicht gleich | lifecyclestage NEQ 'subscriber' |
CONTAINS_TOKEN |
Enthält | email CONTAINS_TOKEN 'gmail' |
NOT_CONTAINS_TOKEN |
Enthält nicht | email NOT_CONTAINS_TOKEN 'test' |
GT |
Größer als | createdate GT '2026-01-01' |
LT |
Kleiner als | createdate LT '2026-12-31' |
GTE |
Größer oder gleich | deal_amount GTE 10000 |
LTE |
Kleiner oder gleich | deal_amount LTE 50000 |
HAS_PROPERTY |
Hat Wert | phone HAS_PROPERTY |
NOT_HAS_PROPERTY |
Fehlender Wert | phone NOT_HAS_PROPERTY |
Ein Unternehmen erstellen
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;
};
// Beispiel
const company = await createCompany({
name: 'Acme Corporation',
domain: 'acme.com',
industry: 'Technology',
employees: 500,
revenue: 50000000,
city: 'San Francisco',
state: 'CA',
country: 'USA'
});
Objekte verknüpfen
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;
};
// Beispiel
await associateContactWithCompany('12345', '67890');
Verknüpfungstypen
| Verknüpfung | Typ-ID | Richtung |
|---|---|---|
| Kontakt → Unternehmen | 1 | Kontakt ist mit Unternehmen verknüpft |
| Unternehmen → Kontakt | 1 | Unternehmen hat verknüpften Kontakt |
| Deal → Kontakt | 3 | Deal ist mit Kontakt verknüpft |
| Deal → Unternehmen | 5 | Deal ist mit Unternehmen verknüpft |
| Ticket → Kontakt | 16 | Ticket ist mit Kontakt verknüpft |
| Ticket → Unternehmen | 15 | Ticket ist mit Unternehmen verknüpft |
Einen Deal erstellen
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;
};
// Deal erstellen
const deal = await createDeal({
name: 'Acme Corp - Enterprise License',
amount: 50000,
stage: 'qualification',
closeDate: '2026-06-30',
type: 'newbusiness',
description: 'Enterprise annual subscription'
});
// Verknüpfen mit Unternehmen und Kontakt
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-Phasen (Standard-Pipeline)
| Phase | Interner Wert |
|---|---|
| Termine vereinbart | appointmentscheduled |
| Kaufinteressent qualifiziert | qualifiedtobuy |
| Präsentation geplant | presentationscheduled |
| Entscheidungsträger überzeugt | decisionmakerboughtin |
| Vertrag versendet | contractsent |
| Abschluss gewonnen | closedwon |
| Abschluss verloren | closedlost |
Webhooks
Webhooks konfigurieren
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;
};
// Beispiel
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 erstellt: ${webhook.id}`);
Webhook-Ereignistypen
| Ereignistyp | Auslöser |
|---|---|
contact.creation |
Neuer Kontakt erstellt |
contact.propertyChange |
Kontakteigenschaft aktualisiert |
contact.deletion |
Kontakt gelöscht |
company.creation |
Neues Unternehmen erstellt |
company.propertyChange |
Unternehmenseigenschaft aktualisiert |
deal.creation |
Neuer Deal erstellt |
deal.stageChange |
Deal-Phase geändert |
deal.propertyChange |
Deal-Eigenschaft aktualisiert |
ticket.creation |
Neues Ticket erstellt |
ticket.propertyChange |
Ticket-Eigenschaft aktualisiert |
Webhooks verarbeiten
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);
// Webhook-Signatur prüfen
const isValid = verifyWebhookSignature(payload, signature, process.env.HUBSPOT_CLIENT_SECRET);
if (!isValid) {
console.error('Ungültige Webhook-Signatur');
return res.status(401).send('Nicht autorisiert');
}
const events = req.body;
for (const event of events) {
console.log(`Ereignis: ${event.eventType}`);
console.log(`Objekt: ${event.objectType} - ${event.objectId}`);
console.log(`Eigenschaft: ${event.propertyName}`);
console.log(`Wert: ${event.propertyValue}`);
// Event-Routing
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')
);
}
Ratenbegrenzung
Ratenbegrenzungen verstehen
| Stufe | Anfragen/Sekunde | Anfragen/Tag |
|---|---|---|
| Kostenlos/Starter | 100 | 100.000 |
| Professional | 200 | 500.000 |
| Enterprise | 400 | 1.000.000 |
Überschreitest du Limits, erhältst du HTTP 429 (Too Many Requests).
Ratenbegrenzungs-Header
| Header | Beschreibung |
|---|---|
X-HubSpot-RateLimit-Second-Limit |
Maximale Anfragen pro Sekunde |
X-HubSpot-RateLimit-Second-Remaining |
Verbleibende Anfragen in dieser Sekunde |
X-HubSpot-RateLimit-Second-Reset |
Sekunden bis zur Zurücksetzung des Sekundenlimits |
X-HubSpot-RateLimit-Daily-Limit |
Maximale Anfragen pro Tag |
X-HubSpot-RateLimit-Daily-Remaining |
Verbleibende Anfragen heute |
X-HubSpot-RateLimit-Daily-Reset |
Sekunden bis zur Zurücksetzung des Tageslimits |
Implementierung der Ratenbegrenzungsbehandlung
const makeRateLimitedRequest = async (endpoint, options = {}, maxRetries = 3) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await hubspotRequest(endpoint, options);
// Rate-Limit-Info loggen
const remaining = response.headers.get('X-HubSpot-RateLimit-Second-Remaining');
if (remaining < 10) {
console.warn(`Wenig verbleibende Ratenbegrenzung: ${remaining}`);
}
return response;
} catch (error) {
if (error.message.includes('429') && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
console.log(`Ratenbegrenzt. Erneuter Versuch in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
};
// Rate-Limiter-Klasse
class HubSpotRateLimiter {
constructor(requestsPerSecond = 90) { // Unter Limit bleiben
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;
}
}
Checkliste für die Produktionsbereitstellung
Vor dem Go-Live:
- [ ] Private App oder OAuth 2.0 Authentifizierung einsetzen
- [ ] Tokens sicher speichern (verschlüsselte DB)
- [ ] Automatische Token-Aktualisierung implementieren
- [ ] Ratenbegrenzung und Warteschlange einrichten
- [ ] Webhook-Endpunkt über HTTPS betreiben
- [ ] Fehlerbehandlung für alle API-Aufrufe
- [ ] Logging für alle API-Interaktionen
- [ ] Ratenbegrenzung überwachen
- [ ] Runbook für Standardprobleme bereitstellen
Anwendungsfälle aus der Praxis
CRM-Synchronisation
Ein SaaS-Unternehmen synchronisiert Kundendaten:
- Herausforderung: Manuelle Dateneingabe zwischen App und HubSpot
- Lösung: Echtzeit-Synchronisation via Webhooks und API
- Ergebnis: Keine manuelle Eingabe, 100% Datengenauigkeit
Lead-Routing
Eine Marketingagentur automatisiert die Lead-Verteilung:
- Herausforderung: Langsame Lead-Antwortzeiten
- Lösung: Webhook-gesteuertes Routing an Vertrieb
- Ergebnis: 5 Minuten Reaktionszeit, 40% Conversion-Steigerung
Fazit
Die HubSpot API bietet umfassende CRM- und Marketing-Automatisierung. Wichtig für die Implementierung:
- OAuth 2.0 für Multi-Tenant-Apps, private Apps für interne Integrationen nutzen
- Ratenbegrenzungen staffeln sich je nach Tarif (100–400 Anfragen/Sekunde)
- Webhooks ermöglichen Echtzeit-Synchronisation
- CRM-Objekte unterstützen Verknüpfungen und Custom Fields
- Apidog optimiert API-Tests und Teamarbeit
FAQ-Bereich
Wie authentifiziere ich mich mit der HubSpot API?
Nutze OAuth 2.0 für Multi-Tenant-Apps oder private Apps für Single-Portal-Integrationen. API-Schlüssel sind veraltet.
Was sind die HubSpot Ratenbegrenzungen?
Je nach Tarif: 100 (Free), 200 (Professional), 400 (Enterprise) Anfragen/Sekunde, tägliche Limits 100.000–1.000.000 Anfragen.
Wie erstelle ich einen Kontakt in HubSpot?
POST an /crm/v3/objects/contacts mit Eigenschaften wie email, firstname, lastname und Custom Fields.
Kann ich benutzerdefinierte Eigenschaften erstellen?
Ja, nutze die Properties API für Custom Fields pro Objekttyp.
Wie funktionieren Webhooks in HubSpot?
Konfiguriere Webhooks in den App-Einstellungen. HubSpot sendet POST-Requests an deinen Endpunkt bei Ereignissen.
Top comments (0)