Chapter 11 · Security · Performance · Production Operations
Security performance
Production operation
From token management, rate limit measures, error handling design, monitoring and alert settings. Systematically acquire practical knowledge for operating the HubSpot linked system safely, stably, and quickly in a production environment.
11-1 Token management security
Securely store and rotate Private App tokens and OAuth tokens.
| Management method | Purpose | Recommendation level |
| AWS Secrets Manager / GCP Secret Manager | Token management in production environment | ⭐⭐⭐ Highly recommended |
| Environment variables (CI/CD secrets) | Use with CI/CD such as GitHub Actions | ⭐⭐⭐ |
| Vault(HashiCorp) | enterprise environment | ⭐⭐⭐ |
| .env file (.gitignore completed) | For local development only | ⭐⭐ (Not available for actual production) |
| written directly in the code | Prohibited | ❌ Absolutely prohibited |
Node.js — Get token from AWS Secrets Manager
import { SecretsManagerClient, GetSecretValueCommand }
from '@aws-sdk/client-secrets-manager';
const secretsClient =
new SecretsManagerClient({ region:
'ap-northeast-1' });
async function getHubSpotToken() {
const command =
new GetSecretValueCommand({
SecretId:
'prod/hubspot/access-token',
});
const response =
await secretsClient.send(command);
return JSON.parse(response.SecretString).HUBSPOT_ACCESS_TOKEN;
}
let tokenCache = { value:
null, fetchedAt:
0 };
const TOKEN_TTL =
10 *
60 *
1000;
async function getCachedToken() {
if (tokenCache.value && Date.now() - tokenCache.fetchedAt < TOKEN_TTL) {
return tokenCache.value;
}
tokenCache.value =
await getHubSpotToken();
tokenCache.fetchedAt = Date.now();
return tokenCache.value;
}
⚠ Token rotation:
If your Private App token is compromised, you can immediately report it in your HubSpot admin.regenerationPlease.
Old tokens will be automatically invalidated.
Also incorporate regular rotation (recommended every 90 days) into your security policy.
11-2 Comprehensive error handling
Understand HubSpot API error patterns and implement appropriate recovery actions.
| HTTP status | cause | How to deal with it |
400 Bad Request | Invalid request format/value | Enhanced logging/validation |
401 Unauthorized | Token is invalid/expired | Reacquire token and retry |
403 Forbidden | Insufficient scope/no authority | Add and notify required scopes |
404 Not Found | Specify non-existent ID | Check deleted/incorrect ID |
409 Conflict | Duplicate data (unique constraint violation) | Get and update existing records |
429 Too Many Requests | Rate limit exceeded | Wait for Retry-After and retry |
500/502/503 | Temporary errors on the HubSpot side | Retry with exponential backoff |
Node.js — Production-grade error handling class
class HubSpotApiError
extends Error {
constructor(status, message, body) {
super(message);
this.status = status;
this.body = body;
this.name =
'HubSpotApiError';
}
get isRetryable() {
return [
429,
500,
502,
503,
504].includes(
this.status);
}
get isTokenError() {
return this.status ===
401;
}
}
async function hubspotRequest(fn, { maxRetries =
4, onRetry } = {}) {
for (
let attempt =
0; attempt < maxRetries; attempt++) {
try {
return await fn();
}
catch (err) {
const status = err.response?.status ??
0;
const apiErr =
new HubSpotApiError(status, err.message, err.response?.data);
if (!apiErr.isRetryable || attempt === maxRetries -
1) {
console.error(JSON.stringify({
level:
'error',
service:
'hubspot',
status,
message: err.message,
attempt: attempt +
1,
ts:
new Date().toISOString(),
}));
throw apiErr;
}
const retryAfter = err.response?.headers[
'retry-after'];
const delay = retryAfter
? parseInt(retryAfter) *
1000
: Math.min(Math.pow(
2, attempt) *
1000,
32000);
onRetry?.({ attempt, delay, status });
console.warn(
`Retry ${attempt + 1}/${maxRetries} in ${delay}ms (status: ${status})`);
await new Promise(r => setTimeout(r, delay));
}
}
}
11-3 Strategic handling of rate limits
Avoid rate limits by combining large data processing, parallel execution, and nightly batching.
Node.js — Production parallel control + rate limit management
import pLimit
from 'p-limit';
import Bottleneck
from 'bottleneck';
const limiter =
new Bottleneck({
reservoir:
80,
reservoirRefreshAmount:
80,
reservoirRefreshInterval:
10000,
maxConcurrent:
5,
minTime:
100,
});
const rateLimitedCall = (fn) => limiter.schedule(fn);
const fetchWithRateLimit =
async (ids) => {
const results =
await Promise.all(
ids.map(id => rateLimitedCall(() =>
client.crm.contacts.basicApi.getById(id, [
'email',
'firstname'])
))
);
return results;
};
📊 Rate limiting strategy priorities
① Use Batch API (top priority):100 items can be processed in 1 request. 100x more efficient than single API
② Get filters using Search API:Narrow down to only the necessary data rather than acquiring all data
③ Limit the number of concurrent executions:Control the degree of parallelism with p-limit and Bottleneck
④ Night/off-peak execution:Large batches are executed at midnight JST (daytime UTC)
⑤ Utilize cache:Cache data that changes infrequently (property list, etc.) in Redis
11-4 Monitoring/alert design
Build a monitoring system that quickly detects abnormalities in the production environment and minimizes business impact.
📈 Metrics to measure
API error rate(Target: <0.1%)
429 Frequency of occurrence(Frequent occurrence of rate limits is a sign of design review)
Response time p99(Goal: <3 seconds)
Webhook processing delay(Goal: <5 seconds)
Sync job success rate(Target: >99.5%)
🚨 Approximate alert threshold
Instant notifications (PagerDuty):
Error rate >5% for 5 minutes
401/403 error occurred (possible token leakage)
Slack notifications:
429 more than 50 times per hour
Synchronization job has been incomplete for more than 30 minutes
Node.js — Structured logging + metrics recording
import { createLogger, transports, format }
from 'winston';
import { Counter, Histogram }
from 'prom-client';
const logger = createLogger({
format: format.combine(format.timestamp(), format.json()),
transports: [
new transports.Console()],
});
const apiCallCounter =
new Counter({
name:
'hubspot_api_calls_total',
help:
'Total HubSpot API calls',
labelNames: [
'method',
'endpoint',
'status'],
});
const apiDurationHistogram =
new Histogram({
name:
'hubspot_api_duration_seconds',
help:
'HubSpot API call duration',
labelNames: [
'endpoint'],
buckets: [
0.1,
0.5,
1,
2,
5],
});
async function trackedApiCall(endpoint, fn) {
const endTimer = apiDurationHistogram.startTimer({ endpoint });
try {
const result =
await fn();
apiCallCounter.inc({ endpoint, status:
'success' });
logger.info({ msg:
'api_call_success', endpoint });
return result;
}
catch (err) {
const status = err.status ??
'unknown';
apiCallCounter.inc({ endpoint, status: String(status) });
logger.error({ msg:
'api_call_error', endpoint, status, error: err.message });
throw err;
}
finally {
endTimer();
}
}
Node.js — Health check endpoint
import express
from 'express';
const app = express();
app.get(
'/health', (req, res) => {
res.status(
200).json({ status:
'ok', timestamp:
new Date().toISOString() });
});
app.get(
'/readiness',
async (req, res) => {
try {
await client.crm.properties.coreApi.getAll(
'contacts');
res.status(
200).json({ status:
'ready', hubspot:
'connected' });
}
catch (err) {
res.status(
503).json({ status:
'not_ready', hubspot:
'disconnected', error: err.message });
}
});
app.get(
'/metrics',
async (req, res) => {
res.set(
'Content-Type',
'text/plain');
res.send(
await register.metrics());
});
11-5 Checklist for production deployment
Organize the things you need to check before going live to the production environment.
| category | Things to check | completion |
| security | API keys are managed by environment variables/secret manager | □ |
| Webhook signature verification is implemented | □ |
| .env file is included in .gitignore | □ |
| Private App has minimal scope | □ |
| error handling | Try/catch is implemented for all API calls | □ |
| Retry with exponential backoff is implemented | □ |
| Webhook handler supports idempotent processing | □ |
| performance | Preferentially using Batch API | □ |
| Concurrency is appropriately limited | □ |
| Immutable data (property definitions, etc.) is cached | □ |
| surveillance | Structured logs are being forwarded to CloudWatch/Datadog, etc. | □ |
| Error rate/rate limit alerts are set | □ |
| /health//readiness endpoints are implemented | □ |
| test | End-to-end testing completed in Sandbox | □ |
| A road test for processing large amounts of data has been completed. | □ |
11-6 Summary of this chapter
✅ Chapter 11 Checklist
- You can securely manage tokens using AWS Secrets Manager etc.
- Understand the appropriate actions to take for each HTTP error status
- Can implement retry class with exponential backoff
- You can control the rate limit using a token bucket method using Bottleneck.
- Design metrics to monitor and alert thresholds
- Can implement structured logging + Prometheus metrics recording
- Can implement health check/readiness endpoints
- You can use the pre-go-live checklist
🎉 HubSpot Developer Handbook — Congratulations!
From Chapter 0 to Chapter 11, we've covered everything you need to know to be a HubSpot developer.
Starting with CLI environment construction, CRM API/CMS development/custom object/
Workflow expansion, Webhooks, OAuth, MCP Server, and production operations——
You now have the technology stack to pass as a HubSpot full stack developer.
- Chapter 0: Complete map of HubSpot developers
- Chapter 1: CLI & Development Workflow
- Chapter 2: CRM API Complete Guide
- Chapter 3:Marketing & CMS API
- Chapter 4: Custom Object & Schema Design
- Chapter 5: CMS Themes & Templates
- Chapter 6: Modular design
- Chapter 7: Workflow Extension & Custom Code Action
- Chapter 8: Webhooks & event-driven design
- Chapter 9:App & OAuth 2.0
- Chapter 10: HubSpot MCP Server & AI development collaboration
- Chapter 11: Security, performance, and production operations