
Letar du fortfarande efter en HubSpot API-nyckel för att koppla ihop din HubSpot API-integration? Sluta leta. HubSpot tog bort statiska API-nycklar den 30 november 2022, och det nuvarande Node-SDK:t (@hubspot/api-client, nu på v14) accepterar ändå inte en sådan. Rätt autentiseringsuppgift för ett internt verktyg med ett enda konto är ett private app-token, och den här guiden bygger en riktig synk mellan HubSpot och ett internt verktyg i både Node och Python, från ditt första create contact-anrop till en signaturvaliderad webhook.
Snabbt svar: En HubSpot API-integration låter ett skräddarsytt internt verktyg läsa och skriva CRM-data via HubSpots v3 REST-API. För ett internt verktyg med ett enda konto autentiserar du med ett private app-token (HubSpot fasade ut API-nycklar 2022), och synkar sedan ändringar i realtid med webhooks istället för polling.
Här är vad du kommer att bygga:
- Autentisering med private app-token plus ditt första
create contact-anrop i Node och Python - En webhook-mottagare som validerar
X-HubSpot-Signature-v3innan den litar på en payload - En 429-säker synk i batchar om 100 till en intern ärende- eller ERP-post
Hur fungerar HubSpot API-integration för skräddarsydda interna verktyg?
En HubSpot API-integration kopplar ett skräddarsytt internt verktyg (en ärendeapp, ett ERP, en faktureringspanel, en kundportal) till HubSpots CRM via dess v3 REST-API. Ditt verktyg läser och skriver CRM-objekt (kontakter, affärer, företag eller anpassade objekt) över HTTPS med ett private app-token, och ändringar i realtid strömmar tillbaka via webhooks.
Tänk på HubSpots CRM som en databas du pratar med över HTTP. Varje post är ett objekt med en typ och ett ID. hubspot crm api integration-lösningen du bygger gör två saker: den skjuter in data i HubSpot (skapar en kontakt när ett ärende öppnas) och hämtar ut data från det (läser en affär när din interna panel renderas).
Synken körs i en av två riktningar. En enkelriktad synk kopierar ändringar från HubSpot till ditt verktyg, eller från ditt verktyg till HubSpot. En dubbelriktad synk gör båda och behöver loopskydd, vilket vi går igenom senare. Och istället för att fråga HubSpot "något nytt?" varje minut (polling) registrerar du en webhook så att HubSpot säger till i samma sekund en post ändras.
Om du hellre vill äga din data helt och hållet än integrera mot en hostad CRM alls, är det värt att väga en open source-CRM som du egenhostar mot detta innan du bestämmer dig. Men om HubSpot redan är din sanningskälla är API:et hur allt annat pratar med det.
För ett internt verktyg med ett enda konto behöver du varken OAuth eller en listning i en app-marknadsplats. Ett private app-token och en webhook är hela integrationen.
Autentisering 2026: Varför det inte längre finns någon HubSpot API-nyckel
För HubSpot API-autentisering på ett internt verktyg med ett enda konto, använd ett private app-token. Det är ett statiskt bearer-token du genererar en gång i ditt HubSpot-konto, avgränsat till exakt de objekt ditt verktyg rör vid. Det finns inget refresh-flöde och ingen utgångstid. OAuth finns för publika appar med flera konton, inte för den panel ditt driftteam kör internt.
Private app-token jämfört med den utfasade API-nyckeln
Här är fällan som snubblar hälften av utvecklarna som hamnar på den här SERP:en. HubSpot fasade ut API-nycklar den 30 november 2022, och de är helt osupportade nu. Autokomplettering föreslår fortfarande "hubspot api key" för att musklerna inte har hunnit ikapp, men det finns ingen nyckel att hämta. Använd istället en hubspot private app: skapa den i Settings, ge den de behörigheter (scopes) den behöver och kopiera access-token från Auth-fliken. HubSpots översikt över private apps beskriver hela uppsättningen.
| Metod | Användningsfall | Går ut eller kräver refresh? | Bäst för |
|---|---|---|---|
| API-nyckel | Borttagen | Utfasad nov 2022 | Ingenting, den är utfasad |
| Private app-token | Internt verktyg med ett enda konto | Nej, statisk, ingen refresh | Ditt interna verktyg, standardvalet här |
| OAuth 2.0 | Publik app eller app med flera konton | Ja, token går ut på ungefär 6 timmar och behöver refreshas | Appar du listar för andra företags portaler |
| Service Key (öppen beta, feb 2026) | Kontonivå, dataenbart uppgift | Kontoavgränsad, enligt dokumentationen | Dataenbart serverjobb, fortfarande beta |
Två regler om själva token. Ge minsta möjliga behörighet: om ditt verktyg bara läser affärer och skriver kontakter, be om crm.objects.contacts.write och crm.objects.deals.read, inget mer. Och håll token i en miljövariabel eller en hemlighetshanterare, skickad i Authorization: Bearer-headern, aldrig hårdkodad och aldrig skickad till webbläsaren.
Slutsatsen är enkel. För ett internt verktyg, använd ett private app-token. Ta OAuth först om detta senare blir en publik app med flera konton som andra företag installerar i sina egna portaler.
Ditt första HubSpot API-anrop: Skapa en kontakt i Node och Python
Det klassiska första anropet är create contact, och de officiella SDK:erna gör det till några rader. Installera klienten, initiera den med ditt private app-token från miljön, och skapa sedan en kontakt och läs tillbaka en affär. Det är samma mönster du kommer återanvända för företag, ärenden och hubspot custom objects api-anrop, bara objekttypen ändras.
Här är Node-versionen med @hubspot/api-client (v14):
// npm i @hubspot/api-client (v14.x)
import { Client } from "@hubspot/api-client";
// Private app-token från en hemlighetshanterare eller miljövariabel, aldrig hårdkodad
const hubspot = new Client({ accessToken: process.env.HUBSPOT_PRIVATE_APP_TOKEN });
// Create a contact
const { id } = await hubspot.crm.contacts.basicApi.create({
properties: {
email: "[email protected]",
firstname: "Ada",
lastname: "Lovelace",
lifecyclestage: "lead",
},
associations: [],
});
console.log("Created contact", id);
// Read a deal by ID
const deal = await hubspot.crm.deals.basicApi.getById(
"1234567890",
["dealname", "amount", "dealstage"],
);
console.log(deal.properties.dealname, deal.properties.amount);Och samma sak i Python med hubspot-api-client (v12):
# pip install hubspot-api-client (v12.x)
import os
from hubspot import HubSpot
from hubspot.crm.contacts import SimplePublicObjectInputForCreate
# Private app-token från miljön, inte källkodshanteringen
client = HubSpot(access_token=os.environ["HUBSPOT_PRIVATE_APP_TOKEN"])
# Create a contact
contact = client.crm.contacts.basic_api.create(
simple_public_object_input_for_create=SimplePublicObjectInputForCreate(
properties={
"email": "[email protected]",
"firstname": "Ada",
"lastname": "Lovelace",
"lifecyclestage": "lead",
}
)
)
print("Created contact", contact.id)
# Read a deal by ID
deal = client.crm.deals.basic_api.get_by_id(
deal_id="1234567890",
properties=["dealname", "amount", "dealstage"],
)
print(deal.properties["dealname"], deal.properties["amount"])Ett tips: testa mot en HubSpot utvecklarsandbox, aldrig mot produktion först. Ett felformat create-anrop i produktion lämnar riktiga skräpposter som ditt säljteam måste städa upp. Token, behörigheter och objektmodellen beter sig identiskt i sandboxen.
Hur synkar du HubSpot till ett internt verktyg i realtid?
Använd webhooks, inte polling. Registrera en webhook-prenumeration i din private app för objektet och händelsen du bryr dig om (säg deal.propertyChange), peka den mot en HTTPS-endpoint du hostar, och HubSpot POSTar en liten JSON-array till dig i samma sekund som en matchande ändring inträffar. Polla bara när det inte finns någon prenumeration för det du behöver bevaka.
Vinsten är effektivitet. Polling frågar "något nytt?" varje minut och förbrukar din rate limit medan den gör det; en webhook säger bara till i samma sekund en affär ändras. Den skillnaden spelar roll i stor skala, och webhooks är mainstream nu, inte exotiska: Postmans 2025 State of the API Report, en undersökning av mer än 5 700 utvecklare, fann att ungefär hälften av teamen förlitar sig på dem.
Registrera prenumerationen under din private apps Webhooks-flik, sätt målets URL och välj händelserna. HubSpot skickar en array av händelseobjekt, var och en med subscriptionType, objectId och vad som ändrades. Här är en mottagarstubb i Node med Express:
import express from "express";
const app = express();
// Capture the raw body: you need the exact bytes to validate the signature next
app.use(express.json({
verify: (req, _res, buf) => { req.rawBody = buf.toString("utf8"); },
}));
// HubSpot POSTs an array of events to this URL
app.post("/webhooks/hubspot", (req, res) => {
const events = req.body; // [{ subscriptionType: "deal.propertyChange", objectId: 1234, ... }]
for (const event of events) {
console.log("HubSpot event:", event.subscriptionType, event.objectId);
// Do NOT trust this payload yet. The next section validates it before we act.
}
res.sendStatus(200);
});
app.listen(3000, () => console.log("Listening on :3000"));Det är här bygget blir på riktigt. Säg att du synkar en affär till en liten tillverkares ERP-post: webhooken avfyras, din handler skapar eller uppdaterar matchande ERP-ärende, och ditt driftteam ser ändringen utan att röra HubSpot. Det är samma realtidsansats vi använder för att synka en röstassistent till ett CRM, bara att triggern är en fältändring istället för ett telefonsamtal. En varning: stubben ovan litar på allt som POSTas till den. Fixa det innan du går live.
Validera webhook-signaturer (v3) så att du aldrig litar på en förfalskad payload
Validera varje inkommande webhook med v3-signaturen. HubSpot signerar varje förfrågan med din app-hemlighet och skickar två headers, X-HubSpot-Signature-v3 och X-HubSpot-Request-Timestamp. Avvisa allt äldre än 5 minuter, bygg om källsträngen som metod + fullständig URL + rå body + tidsstämpel, HMAC-SHA256:a den med app-hemligheten, base64-koda och jämför i konstant tid.
Om du hoppar över det här kan vem som helst som gissar din webhook-URL förfalska en affärsuppdatering. Validering är inte valfritt. HubSpots dokument om att validera förfrågningar och changeloggen för v3-signaturer beskriver exakt receptet. Här är det som en färdig Express-middleware:
import crypto from "crypto";
const CLIENT_SECRET = process.env.HUBSPOT_APP_SECRET; // from your private app settings
const MAX_AGE_MS = 5 * 60 * 1000; // reject anything older than 5 minutes
export function validateHubSpotSignature(req, res, next) {
const signature = req.header("X-HubSpot-Signature-v3");
const timestamp = req.header("X-HubSpot-Request-Timestamp");
// 1. Reject stale requests (replay protection)
if (!signature || !timestamp || Date.now() - Number(timestamp) > MAX_AGE_MS) {
return res.sendStatus(401);
}
// 2. Rebuild the exact source string: method + full URL + raw body + timestamp
const uri = `https://${req.get("host")}${req.originalUrl}`;
const source = `${req.method}${uri}${req.rawBody}${timestamp}`;
// 3. HMAC-SHA256 with the app secret, base64-encoded
const hash = crypto
.createHmac("sha256", CLIENT_SECRET)
.update(source, "utf8")
.digest("base64");
// 4. Constant-time compare against the header
const expected = Buffer.from(hash);
const received = Buffer.from(signature);
if (expected.length !== received.length ||
!crypto.timingSafeEqual(expected, received)) {
return res.sendStatus(401);
}
next();
}Samma kontroll som en Python-funktion, så att båda stackarna täcks:
import base64
import hashlib
import hmac
import os
import time
CLIENT_SECRET = os.environ["HUBSPOT_APP_SECRET"].encode("utf-8")
MAX_AGE_MS = 5 * 60 * 1000 # 5 minutes
def is_valid_signature(method, uri, body, signature, timestamp):
# 1. Reject stale requests
if not signature or not timestamp:
return False
if int(time.time() * 1000) - int(timestamp) > MAX_AGE_MS:
return False
# 2. method + full URL + raw body + timestamp
source = f"{method}{uri}{body}{timestamp}".encode("utf-8")
# 3. HMAC-SHA256, base64
digest = hmac.new(CLIENT_SECRET, source, hashlib.sha256).digest()
expected = base64.b64encode(digest).decode("utf-8")
# 4. Constant-time compare
return hmac.compare_digest(expected, signature)Fällan som kostar folk en eftermiddag: HubSpot signerar den fullständiga mål-URL:en, schema och host och sökväg tillsammans. Bakom en proxy, en lastbalanserare eller en ngrok-tunnel kan req.get("host") rapportera den interna hosten istället för den publika HubSpot signerade. Om valideringen fortsätter att misslyckas på en payload du är säker på är legitim, logga exakt den URI du byggde om och jämför den mot din publika webhook-URL, tecken för tecken.
Rate limits, 429:or och batch-API:et: vad vi körde i produktion
Private apps får ungefär 10 förfrågningar per sekund (100 per 10 sekunder på Free/Starter, 190 per 10 sekunder på Pro/Enterprise) med ett dagligt tak mellan 250 000 och 1 000 000. Fällan: CRM Search är separat begränsat till 4 förfrågningar per sekund, och batch-endpoints tar emot högst 100 poster per förfrågan. HubSpots användarriktlinjer listar nivåerna.
| Nivå | Per 10 s | Per sekund | Dagligt tak | Anteckningar |
|---|---|---|---|---|
| Free / Starter (private app) | 100 | ~10 | 250 000 | CRM Search separat begränsat till 4 förfr/s |
| Pro / Enterprise (private app) | 190 | ~19 | upp till 1 000 000 | Batch-endpoints max 100 poster per förfrågan |
Här är var teorin mötte en röd staging-panel. Under en backfill i våras skjöt vi in ungefär 8 000 befintliga poster i HubSpot från ett internt ärendeverktyg och berikade var och en med en CRM Search-uppslagning. Vi körde @hubspot/api-client v14 på Node-sidan och hubspot-api-client v12 för en Python-berikningsworker. Massskrivningarna gick fint. Search-anropen föll ihop inom en minut, för att vår worker sköt Search-anrop i ungefär 15 förfr/s mot ett hårt tak på 4 förfr/s som vi inte hade budgeterat för separat.
Två förändringar löste det. Först en retry-wrapper som läser X-HubSpot-RateLimit-*-svarshuvudena och backar av vid en 429:
// Wrap any HubSpot call; retries on 429 with exponential backoff
async function withRetry(fn, maxRetries = 5) {
let attempt = 0;
while (true) {
try {
return await fn();
} catch (err) {
const status = err.code ?? err.response?.status;
if (status !== 429 || attempt >= maxRetries) throw err;
// Honor HubSpot's reset window if the header is present
const headers = err.response?.headers ?? {};
const resetMs = Number(headers["x-hubspot-ratelimit-interval-milliseconds"]) || 0;
const backoff = Math.max(resetMs, 2 ** attempt * 500); // 0.5s, 1s, 2s, 4s...
console.warn(`429 hit, retry ${attempt + 1} in ${backoff}ms`);
await new Promise((r) => setTimeout(r, backoff));
attempt++;
}
}
}För det andra slutade vi skriva poster en i taget. Batch-endpointen tar upp till 100 poster per POST /crm/v3/objects/{objectType}/batch/create, så vi delade upp backfillen i 80 batch-anrop istället för 8 000 enskilda POST:ar:
// HubSpot batch endpoints accept at most 100 records per request
function chunk(arr, size = 100) {
const out = [];
for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size));
return out;
}
// POST /crm/v3/objects/contacts/batch/create, chunked to 100 at a time
async function batchCreateContacts(records) {
for (const group of chunk(records, 100)) {
const inputs = group.map((r) => ({
properties: { email: r.email, firstname: r.firstName, lastname: r.lastName },
associations: [],
}));
await withRetry(() => hubspot.crm.contacts.batchApi.create({ inputs }));
console.log(`Wrote ${group.length} contacts`);
}
}Att begränsa Search-workern till 4 förfr/s och batcha skrivningarna gjorde ett körjobb som drunknade i retries till ett som blev klart i tysthet. Om du kommer ihåg ett enda tal från det här avsnittet, låt det vara 4: CRM Search-taket är gränsen som biter i produktion, och det är den varenda sammanställningsartikel glömmer att nämna. Det gamla taket på "10 för kontakter" i batch är förresten borta; nuvarande gräns är 100 tvärs över alla objekttyper.
Att gå dubbelriktat: skriva ändringar tillbaka till HubSpot utan oändliga loopar
En dubbelriktad synk skriver ändringar tillbaka till HubSpot från ditt interna verktyg, samtidigt som den läser in dem. Faran är en återkopplingsloop: din tillbakaskrivning triggar precis den webhook som avfyrade din handler, som skriver igen, i all evighet. Förhindra det med en idempotency-nyckel (hoppa över ändringar du redan har tillämpat) och en källflagga (ignorera inkommande händelser som ditt eget verktyg orsakade).
const processed = new Set(); // use Redis or a unique DB constraint in production
async function writeBackToHubSpot(record) {
// Dedup key: object id + a hash of the change we're about to apply
const key = `${record.id}:${record.updatedHash}`;
if (processed.has(key)) return; // already synced this exact change
processed.add(key);
await withRetry(() =>
hubspot.crm.contacts.basicApi.update(record.id, {
// Tag the source so the resulting webhook is ignored by our own receiver
// (check for source: "internal-tool" before acting on an inbound event)
properties: { internal_status: record.status, last_sync_source: "internal-tool" },
})
);
}Mönstret är litet, men att hoppa över det är hur en synk tyst dubblar din skrivvolym över en natt. När datan väl är ren åt båda hållen matar team ofta in den vidare i en AI SDR-pipeline eller ett rapporteringslager. Nangos handledning för HubSpot-integration är en solid Node-endast referens om du vill ha en andra vinkel på dubbelriktad synk, även om du behöver portera loopskyddstanken själv.
Ska du bygga det här internt eller anlita en integrationspartner?
Bygg internt när synken är liten, stabil och ägd: ett enkelriktat flöde, en handfull objekt och en utvecklare som kan absorbera HubSpots ungefär två gånger per år kommande breaking changes. Anlita en partner när du behöver dubbelriktad synk, modellering av anpassade objekt, eller när ingen i teamet kan äga det löpande underhållet. Den avgörande faktorn är sällan det första bygget; det är vem som bevakar det om ett år.
Här är en ärlig checklista. Bygg det själv om: riktningen är enkelriktad, du synkar standardobjekt, du har en utvecklare som kan hosta en webhook-endpoint, och någon kommer märka när en payload börjar misslyckas. Allt ovanför är din ritning.
Anlita en partner om: du behöver dubbelriktad synk med loopskydd över flera objekt, du modellerar anpassade objekt med typade associationer, du kopplar ihop flera system (HubSpot plus ett ERP plus fakturering), eller personen som skulle underhålla det redan är fullbokad. HubSpot använder datumbaserad API-versionering med breaking changes bara ungefär två gånger per år, vilket låter milt tills en landar under din mest hektiska vecka och det inte finns någon ägare. Den underhållssvansen, inte det första driftsättandet, är det som tyst sänker interna integrationer. Om du hellre inte vill äga det är det där våra tjänster för skräddarsydd CRM-integration kommer in.
Viktiga slutsatser
- Det finns ingen HubSpot API-nyckel längre. Använd ett private app-token för ett internt verktyg med ett enda konto; OAuth är bara för publika appar med flera konton.
- Validera alltid
X-HubSpot-Signature-v3innan du litar på en webhook-payload. Bygg om källsträngen med den fullständiga mål-URL:en. - Respektera 4 förfr/s-taket för CRM Search och batcha stora skrivningar i grupper om 100 med en 429-backoff.
- Föredra webhooks framför polling för realtidssynk, och en HubSpot-integration är bara en del av en bredare AI-verktyg för företag-stack.
Fast i underhållsdelen, eller vill ha ett andra par ögon innan du släpper det? Boka en gratis integrationskonsultation. Ingen press åt något håll; koden ovan är din att köra oavsett.
Om författaren
Mert Batur Gurbuz är medgrundare av Techsy.io, där teamet levererar AI-agenter, automationssystem och röst/SDR-pipelines för B2B-kunder. Han studerar vid University of Birmingham och skriver om det LLM-verktygsstack som Techsy-teamet faktiskt använder i produktion. Meriter: Medgrundare, Techsy.io, University of Birmingham. Anslut på LinkedIn.
Vanliga frågor
Behöver jag fortfarande en HubSpot API-nyckel 2026?
Nej. HubSpot fasade ut statiska API-nycklar den 30 november 2022, och de är helt osupportade. Autokomplettering föreslår fortfarande "hubspot api key" av gammal vana, men det finns inget att hämta. För ett internt verktyg med ett enda konto, skapa en private app i Settings och använd dess access-token istället.
Vad är skillnaden mellan ett private app-token och OAuth för HubSpot?
Ett private app-token är en statisk autentiseringsuppgift för ett enda HubSpot-konto, utan utgångstid och utan refresh-flöde, perfekt för ett internt verktyg. OAuth 2.0 är för publika appar med flera konton som andra företag installerar i sina egna portaler; dess token går ut på ungefär sex timmar och kräver en refresh-cykel.
Vilka är HubSpots API-hastighetsgränser 2026?
Private apps får ungefär 10 förfrågningar per sekund (100 per 10 sekunder på Free/Starter, 190 på Pro/Enterprise) med ett dagligt tak på 250 000 till 1 000 000. CRM Search-API:et är separat begränsat till 4 förfrågningar per sekund, och batch-endpoints tar emot högst 100 poster per förfrågan.
Hur validerar jag en HubSpot-webhooksignatur?
Använd v3-receptet: avvisa förfrågningar där X-HubSpot-Request-Timestamp är äldre än fem minuter, bygg sedan en källsträng av metod plus fullständig mål-URL plus rå body plus tidsstämpel. HMAC-SHA256:a den med din app-hemlighet, base64-koda resultatet och jämför det mot X-HubSpot-Signature-v3 i konstant tid.
Vilket HubSpot-SDK ska jag använda, Node eller Python?
Båda är officiella och underhålls. Node använder @hubspot/api-client (v14) och Python använder hubspot-api-client (v12). De exponerar samma v3 CRM-objektmodell, så välj det som matchar din stack. Den här guiden levererar identisk auth- och signaturvalideringskod i båda språken.
Hur synkar jag HubSpot med ett skräddarsytt internt verktyg i realtid?
Registrera en webhook-prenumeration i din private app för objektet och händelsen du bryr dig om, hosta sedan en HTTPS-endpoint som HubSpot POSTar till när en matchande ändring inträffar. Validera signaturen, skriv sedan in ändringen i ditt interna verktyg. Polla bara när ingen webhook-prenumeration täcker det du behöver.
Vad är en HubSpot Service Key och ska jag använda den?
En Service Key är en kontonivå-uppgift enbart för data som HubSpot släppte i öppen beta i februari 2026. Den är riktad mot serverjobb som bara rör data. För ett vanligt internt verktyg idag är ett private app-token fortfarande det säkrare, bättre dokumenterade standardvalet; behandla Service Keys som beta tills de mognar.
Kan jag testa en HubSpot-integration utan att röra produktion?
Ja. Skapa en HubSpot-utvecklarsandbox och peka ditt private app-token mot den. Behörigheterna, objektmodellen, webhooksen och hastighetsgränserna beter sig som i produktion, så du kan skapa testkontakter och avfyra webhooks utan att lämna kvar skräpposter som ditt säljteam måste städa upp senare.
Hur många poster kan HubSpots batch-API ta åt gången?
Batch-endpointsen (POST /crm/v3/objects/{objectType}/batch/create och dess syskon för update och upsert) tar emot högst 100 poster per förfrågan. Dela upp större payloads i grupper om 100. Det äldre taket på "10 poster för kontakter" som vissa handledningar fortfarande citerar är borttaget; 100 gäller nu tvärs över alla objekttyper.
Ska jag bygga det här internt eller anlita en byrå?
Bygg internt när synken är enkelriktad, använder standardobjekt och har en ägare som kan absorbera HubSpots breaking changes två gånger per år. Anlita en partner för dubbelriktad synk, modellering av anpassade objekt, eller när ingen kan äga underhållet. Det första driftsättandet är enkelt; året av underhåll efteråt är den riktiga kostnaden.