Intisari
HubSpot API memungkinkan pengembang untuk mengintegrasikan hub CRM, pemasaran, penjualan, dan layanan secara terprogram. API ini memakai otentikasi OAuth 2.0 dan aplikasi pribadi, menyediakan endpoint RESTful untuk kontak, perusahaan, penawaran (deals), tiket, dan objek lainnya, serta menerapkan rate limits sesuai tingkat langganan. Panduan berikut membahas setup otentikasi, endpoint inti, webhook, dan strategi integrasi siap-produksi.
Pendahuluan
HubSpot mengelola lebih dari 194.000 akun pelanggan dan miliaran catatan CRM. Untuk pengembang yang membangun integrasi CRM, otomatisasi pemasaran, atau alat penjualan, integrasi HubSpot API sangat penting untuk menjangkau lebih dari 7 juta pengguna.
Faktanya, bisnis kehilangan 15-20 jam per minggu karena entri data manual antar sistem. Integrasi yang solid dengan HubSpot API dapat mengotomatiskan sinkronisasi kontak, pembaruan deal, alur pemasaran, hingga pelaporan lintas platform.
💡 Apidog menyederhanakan pengujian integrasi API. Uji endpoint HubSpot Anda, validasi alur OAuth, cek payload webhook, serta debug masalah otentikasi dalam satu workspace. Impor spesifikasi API, buat mock response, dan kolaborasikan skenario pengujian bersama tim.
Apa Itu HubSpot API?
HubSpot menyediakan API RESTful untuk mengakses data CRM dan fitur otomatisasi pemasaran. API ini mengelola:
- Kontak, perusahaan, penawaran (deals), tiket, dan objek kustom
- Email pemasaran & landing page
- Saluran penjualan dan urutan (sequences)
- Tiket layanan dan percakapan
- Analitik & pelaporan
- Workflow & otomatisasi
- File dan aset
Fitur Utama
| Fitur | Deskripsi |
|---|---|
| Desain RESTful | Metode HTTP standar, respons JSON |
| OAuth 2.0 + Aplikasi Pribadi | Opsi otentikasi fleksibel |
| Webhook | Notifikasi real-time perubahan objek |
| Pembatasan Laju (Rate Limiting) | Batasan berdasarkan tingkatan (100-400 req/detik) |
| Objek CRM | Dukungan objek standar & kustom |
| Asosiasi | Menghubungkan objek (misal kontak-perusahaan) |
| Properti | Bidang kustom untuk semua objek |
| API Pencarian | Penyaringan & pengurutan kompleks |
Ikhtisar Arsitektur API
HubSpot menggunakan API REST versi:
https://api.hubapi.com/
Perbandingan Versi API
| Versi | Status | Otentikasi | Kasus Penggunaan |
|---|---|---|---|
| CRM API v3 | Saat Ini | OAuth 2.0, Aplikasi Pribadi | Semua integrasi baru |
| Automation API v4 | Saat Ini | OAuth 2.0, Aplikasi Pribadi | Pendaftaran workflow |
| Marketing Email API | Saat Ini | OAuth 2.0, Aplikasi Pribadi | Kampanye email |
| Contacts API v1 | Usang | Kunci API (lama) | Migrasi ke v3 |
| Companies API v1 | Usang | Kunci API (lama) | Migrasi ke v3 |
Penting: HubSpot menghentikan otentikasi kunci API. Migrasikan semua integrasi ke OAuth 2.0 atau aplikasi pribadi.
Memulai: Pengaturan Otentikasi
Langkah 1: Buat Akun Pengembang HubSpot
- Kunjungi Portal Pengembang HubSpot
- Login dengan akun HubSpot (atau buat baru)
- Buka menu Aplikasi di dashboard pengembang
- Klik Buat aplikasi
Langkah 2: Pilih Metode Otentikasi
HubSpot mendukung dua metode utama:
| Metode | Terbaik Untuk | Tingkat Keamanan |
|---|---|---|
| OAuth 2.0 | Aplikasi multi-tenant, integrasi publik | Tinggi (token cakupan user) |
| Aplikasi Pribadi | Integrasi internal, satu portal | Tinggi (token cakupan portal) |
Langkah 3: Siapkan Aplikasi Pribadi (Direkomendasikan untuk Integrasi Internal)
- Masuk ke Pengaturan > Integrasi > Aplikasi Pribadi
- Klik Buat aplikasi pribadi
- Pilih scope berikut:
contacts
crm.objects.companies
crm.objects.deals
crm.objects.tickets
automation
webhooks
- Buat token akses & simpan dengan aman:
# .env
HUBSPOT_ACCESS_TOKEN="pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
HUBSPOT_PORTAL_ID="12345678"
Langkah 4: Siapkan OAuth 2.0 (Untuk Multi-Tenant)
- Masuk menu Aplikasi > Buat aplikasi
- Konfigurasi otentikasi:
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;
// URL otorisasi
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()}`;
};
Langkah 5: Tukar Kode untuk Token Akses
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);
// Simpan token ke 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');
}
});
Langkah 6: Refresh Token Akses
Token akses akan kedaluwarsa setelah 6 jam:
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, // Selalu simpan refresh token baru
expiresIn: data.expires_in
};
};
// Middleware validasi token
const ensureValidToken = async (portalId) => {
const installation = await db.installations.findByPortalId(portalId);
// Refresh jika akan expired dalam 30 menit
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;
};
Langkah 7: Melakukan Panggilan API Terotentikasi
Buat klien reusable:
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();
};
// Contoh penggunaan
const contacts = await hubspotRequest('/crm/v3/objects/contacts');
Bekerja dengan Objek CRM
Membuat Kontak
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;
};
// Penggunaan
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}`);
Properti Kontak
| Properti | Tipe | Deskripsi |
|---|---|---|
email |
String | Email utama (unik) |
firstname |
String | Nama depan |
lastname |
String | Nama belakang |
phone |
String | No. telepon |
company |
String | Nama perusahaan |
website |
String | URL situs web |
lifecyclestage |
Enum | lead, marketingqualifiedlead, salesqualifiedlead, opportunity, customer, evangelist, subscriber |
createdate |
DateTime | Dibuat otomatis |
lastmodifieddate |
DateTime | Diperbarui otomatis |
Mendapatkan Kontak
const getContact = async (contactId) => {
const response = await hubspotRequest(`/crm/v3/objects/contacts/${contactId}`);
return response;
};
// Penggunaan
const contact = await getContact('12345');
console.log(`${contact.properties.firstname} ${contact.properties.lastname}`);
console.log(`Email: ${contact.properties.email}`);
Mencari Kontak
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;
};
// Cari semua kontak di perusahaan tertentu
const results = await searchContacts({
filterGroups: [
{
filters: [
{
propertyName: 'company',
operator: 'EQ',
value: 'Acme Corp'
}
]
}
]
});
results.results.forEach(contact => {
console.log(`${contact.properties.email}`);
});
Operator Filter Pencarian
| Operator | Deskripsi | Contoh |
|---|---|---|
EQ |
Sama dengan | company EQ 'Acme' |
NEQ |
Tidak sama dengan | lifecyclestage NEQ 'subscriber' |
CONTAINS_TOKEN |
Mengandung | email CONTAINS_TOKEN 'gmail' |
NOT_CONTAINS_TOKEN |
Tidak mengandung | email NOT_CONTAINS_TOKEN 'test' |
GT |
Lebih besar dari | createdate GT '2026-01-01' |
LT |
Lebih kecil dari | createdate LT '2026-12-31' |
GTE |
Lebih besar/sama dengan | deal_amount GTE 10000 |
LTE |
Lebih kecil/sama dengan | deal_amount LTE 50000 |
HAS_PROPERTY |
Memiliki nilai | phone HAS_PROPERTY |
NOT_HAS_PROPERTY |
Nilai tidak ada | phone NOT_HAS_PROPERTY |
Membuat Perusahaan
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;
};
// Penggunaan
const company = await createCompany({
name: 'Acme Corporation',
domain: 'acme.com',
industry: 'Technology',
employees: 500,
revenue: 50000000,
city: 'San Francisco',
state: 'CA',
country: 'USA'
});
Menghubungkan Objek
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;
};
// Penggunaan
await associateContactWithCompany('12345', '67890');
Jenis Asosiasi
| Asosiasi | ID Tipe | Arah |
|---|---|---|
| Kontak → Perusahaan | 1 | Kontak ke Perusahaan |
| Perusahaan → Kontak | 1 | Perusahaan ke Kontak |
| Penawaran → Kontak | 3 | Deal ke Kontak |
| Penawaran → Perusahaan | 5 | Deal ke Perusahaan |
| Tiket → Kontak | 16 | Tiket ke Kontak |
| Tiket → Perusahaan | 15 | Tiket ke Perusahaan |
Membuat Penawaran (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;
};
// Penggunaan
const deal = await createDeal({
name: 'Acme Corp - Enterprise License',
amount: 50000,
stage: 'qualification',
closeDate: '2026-06-30',
type: 'newbusiness',
description: 'Enterprise annual subscription'
});
// Hubungkan dengan company dan 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 }] }) }
);
Tahap Penawaran (Pipeline Default)
| Tahap | Nilai Internal |
|---|---|
| Janji Temu Terjadwal | appointmentscheduled |
| Memenuhi Syarat untuk Membeli | qualifiedtobuy |
| Presentasi Terjadwal | presentationscheduled |
| Pengambil Keputusan Setuju | decisionmakerboughtin |
| Kontrak Terkirim | contractsent |
| Ditutup Menang | closedwon |
| Ditutup Kalah | closedlost |
Webhook
Mengonfigurasi Webhook
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 properti
})
});
return response;
};
// Penggunaan
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}`);
Jenis Event Webhook
| Jenis Event | Pemicu |
|---|---|
contact.creation |
Kontak baru dibuat |
contact.propertyChange |
Properti kontak diperbarui |
contact.deletion |
Kontak dihapus |
company.creation |
Perusahaan baru dibuat |
company.propertyChange |
Properti perusahaan diperbarui |
deal.creation |
Penawaran baru dibuat |
deal.stageChange |
Tahap penawaran berubah |
deal.propertyChange |
Properti penawaran diperbarui |
ticket.creation |
Tiket baru dibuat |
ticket.propertyChange |
Properti tiket diperbarui |
Menangani Webhook
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);
// Verifikasi 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 ke handler sesuai event
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')
);
}
Pembatasan Laju (Rate Limiting)
Memahami Pembatasan Laju
HubSpot menerapkan rate limit berdasarkan tier langganan:
| Tingkat | Permintaan/Detik | Permintaan/Hari |
|---|---|---|
| Gratis/Pemula | 100 | 100.000 |
| Profesional | 200 | 500.000 |
| Perusahaan | 400 | 1.000.000 |
Jika melewati batas, API akan membalas HTTP 429 (Too Many Requests).
Header Rate Limit
| Header | Deskripsi |
|---|---|
X-HubSpot-RateLimit-Second-Limit |
Batas detik |
X-HubSpot-RateLimit-Second-Remaining |
Sisa permintaan detik ini |
X-HubSpot-RateLimit-Second-Reset |
Detik hingga reset |
X-HubSpot-RateLimit-Daily-Limit |
Batas harian |
X-HubSpot-RateLimit-Daily-Remaining |
Sisa permintaan hari ini |
X-HubSpot-RateLimit-Daily-Reset |
Detik hingga reset harian |
Menerapkan Penanganan Rate Limit
const makeRateLimitedRequest = async (endpoint, options = {}, maxRetries = 3) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await hubspotRequest(endpoint, options);
// Cek rate limit header
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) { // Di bawah 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;
}
}
Daftar Periksa Deployment Produksi
Sebelum go-live, pastikan:
- [ ] Menggunakan aplikasi pribadi atau OAuth 2.0
- [ ] Simpan token secara aman (basis data terenkripsi)
- [ ] Terapkan refresh token otomatis
- [ ] Implementasikan rate limit & antrean permintaan
- [ ] Endpoint webhook di-serve lewat HTTPS
- [ ] Penanganan error yang komprehensif
- [ ] Logging setiap call API
- [ ] Pantau penggunaan rate limit
- [ ] Siapkan runbook untuk masalah umum
Kasus Penggunaan Dunia Nyata
Sinkronisasi CRM
Perusahaan SaaS menyinkronkan data pelanggan:
- Tantangan: Entri data manual antara aplikasi dan HubSpot
- Solusi: Sinkronisasi real-time dengan API & webhook
- Hasil: Nol entri manual, data akurat 100%
Perutean Prospek
Agen pemasaran mengotomasi distribusi prospek:
- Tantangan: Respons prospek lambat
- Solusi: Routing otomatis ke tim sales via webhook
- Hasil: Respons 5 menit, konversi naik 40%
Kesimpulan
HubSpot API memberi kapabilitas CRM dan pemasaran otomatis yang lengkap. Poin utama:
- Gunakan OAuth 2.0 untuk aplikasi multi-tenant, aplikasi pribadi untuk internal
- Rate limit bervariasi (100-400 req/detik)
- Webhook mendukung sinkronisasi real-time
- Objek CRM fleksibel, dukung properti & asosiasi kustom
- Apidog menyederhanakan pengujian API dan kolaborasi tim
Bagian FAQ
Bagaimana cara melakukan otentikasi dengan HubSpot API?
Gunakan OAuth 2.0 untuk aplikasi multi-tenant, atau aplikasi pribadi untuk integrasi satu portal. Kunci API sudah deprecated.
Apa saja batasan laju HubSpot?
Batas mulai 100 request/detik (Gratis) hingga 400/detik (Enterprise), batas harian 100 ribu – 1 juta request.
Bagaimana cara membuat kontak di HubSpot?
Kirim POST ke /crm/v3/objects/contacts dengan properti seperti email, nama depan, nama belakang, dan field kustom lain.
Bisakah saya membuat properti kustom?
Bisa. Pakai API Properti untuk menambah field kustom ke semua objek.
Bagaimana cara kerja webhook di HubSpot?
Konfigurasi webhook melalui pengaturan aplikasi. HubSpot akan mengirim POST ke endpoint Anda saat event terjadi.
Top comments (0)