🟢HubSpot Developer Practical Textbook — 2026 Edition Developer Edition
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.

Best practices for production operations
Monitoring/alert design
Time required: Approximately 90 minutes
11-1 Token management security

Securely store and rotate Private App tokens and OAuth tokens.

Management methodPurposeRecommendation level
AWS Secrets Manager / GCP Secret ManagerToken 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 codeProhibited❌ 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; } // Use caching to avoid requesting the secret manager every time let tokenCache = { value: null, fetchedAt: 0 }; const TOKEN_TTL = 10 * 60 * 1000; // 10 minute cache 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 statuscauseHow to deal with it
400 Bad RequestInvalid request format/valueEnhanced logging/validation
401 UnauthorizedToken is invalid/expiredReacquire token and retry
403 ForbiddenInsufficient scope/no authorityAdd and notify required scopes
404 Not FoundSpecify non-existent IDCheck deleted/incorrect ID
409 ConflictDuplicate data (unique constraint violation)Get and update existing records
429 Too Many RequestsRate limit exceededWait for Retry-After and retry
500/502/503Temporary errors on the HubSpot sideRetry 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) { // structured log 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); // max 32 seconds 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'; // npm install bottleneck // Limit to 80 requests per 10 seconds with Bottleneck (buffer reserved at 80% of 100) const limiter = new Bottleneck({ reservoir: 80, // initial number of tokens reservoirRefreshAmount: 80, // replenishment amount reservoirRefreshInterval: 10000, // Replenish every 10 seconds maxConcurrent: 5, // Number of concurrent executions minTime: 100, // Minimum interval between requests (ms) }); // rate-limited API call wrapper const rateLimitedCall = (fn) => limiter.schedule(fn); // Usage example: efficiently retrieve 1000 contacts in parallel 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'; // Prometheus const logger = createLogger({ format: format.combine(format.timestamp(), format.json()), transports: [new transports.Console()], }); // Prometheus metrics definition 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], }); // API wrapper with metrics 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(); // /health — For monitoring the load balancer's health app.get('/health', (req, res) => { res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() }); }); // /readiness — Details including HubSpot connection app.get('/readiness', async (req, res) => { try { // Confirm communication with HubSpot (using lightweight endpoint) 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 }); } }); // /metrics — for Prometheus scrape 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.

categoryThings to checkcompletion
securityAPI 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 handlingTry/catch is implemented for all API calls
Retry with exponential backoff is implemented
Webhook handler supports idempotent processing
performancePreferentially using Batch API
Concurrency is appropriately limited
Immutable data (property definitions, etc.) is cached
surveillanceStructured logs are being forwarded to CloudWatch/Datadog, etc.
Error rate/rate limit alerts are set
/health//readiness endpoints are implemented
testEnd-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.