Skills Development Securing HubSpot Integrations Best Practices

Securing HubSpot Integrations Best Practices

v20260423
hubspot-security-basics
A comprehensive guide to securing HubSpot API integrations. Details best practices for implementing least-privilege scopes, securely managing access tokens in environment variables, validating webhook signatures (v3), and executing proper token rotation procedures. Essential knowledge for building robust and secure applications connected to HubSpot.
Get Skill
416 downloads
Overview

HubSpot Security Basics

Overview

Security best practices for HubSpot private app tokens, OAuth scopes, webhook signature verification, and secret management.

Prerequisites

  • HubSpot private app or OAuth app configured
  • Understanding of environment variables and secret management

Instructions

Step 1: Least-Privilege Scopes

Only request the scopes your integration actually uses:

Use CaseRequired Scopes
Read contactscrm.objects.contacts.read
Write contactscrm.objects.contacts.read, crm.objects.contacts.write
Read/write dealscrm.objects.deals.read, crm.objects.deals.write
Marketing emailscontent
Formsforms
Contact listscrm.lists.read, crm.lists.write
Propertiescrm.schemas.contacts.read
Custom objectscrm.objects.custom.read, crm.objects.custom.write, crm.schemas.custom.read
Webhooksautomation

Never use: Do not grant all scopes. If you regenerate a private app token, the old token is immediately revoked.

Step 2: Token Storage

# .env (NEVER commit)
HUBSPOT_ACCESS_TOKEN=pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
HUBSPOT_WEBHOOK_SECRET=your-webhook-secret

# .gitignore
.env
.env.local
.env.*.local
// Validate token is present at startup
function validateConfig(): void {
  if (!process.env.HUBSPOT_ACCESS_TOKEN) {
    throw new Error('HUBSPOT_ACCESS_TOKEN is required. See .env.example');
  }
  // Never log the token
  console.log('HubSpot: Token configured', {
    prefix: process.env.HUBSPOT_ACCESS_TOKEN.substring(0, 8) + '...',
  });
}

Step 3: Webhook Signature Verification (v3)

HubSpot sends webhooks with signature verification headers:

import crypto from 'crypto';
import express from 'express';

// HubSpot v3 signature verification
// Header: X-HubSpot-Signature-v3
function verifyHubSpotSignatureV3(
  requestBody: string,
  signature: string,
  timestamp: string,
  clientSecret: string,
  requestUri: string,
  method: string = 'POST'
): boolean {
  // Reject if timestamp is older than 5 minutes (replay protection)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) {
    console.warn('HubSpot webhook timestamp too old');
    return false;
  }

  // v3: HMAC SHA-256 of method + URI + body + timestamp
  const sourceString = `${method}${requestUri}${requestBody}${timestamp}`;
  const expectedSignature = crypto
    .createHmac('sha256', clientSecret)
    .update(sourceString)
    .digest('base64');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Express middleware
const webhookRouter = express.Router();
webhookRouter.post('/hubspot',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['x-hubspot-signature-v3'] as string;
    const timestamp = req.headers['x-hubspot-request-timestamp'] as string;
    const requestUri = `https://${req.headers.host}${req.originalUrl}`;

    if (!verifyHubSpotSignatureV3(
      req.body.toString(), signature, timestamp,
      process.env.HUBSPOT_WEBHOOK_SECRET!, requestUri
    )) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    const events = JSON.parse(req.body.toString());
    // Process events...
    res.status(200).json({ received: true });
  }
);

Step 4: Token Rotation Procedure

# 1. Generate new token in HubSpot
#    Settings > Integrations > Private Apps > [Your App] > Auth tab
#    Click "Rotate token" (old token revoked immediately)

# 2. Update in your secret manager
# AWS Secrets Manager
aws secretsmanager update-secret --secret-id hubspot/production \
  --secret-string '{"access_token":"pat-na1-NEW_TOKEN"}'

# GCP Secret Manager
echo -n "pat-na1-NEW_TOKEN" | gcloud secrets versions add hubspot-token --data-file=-

# 3. Restart/redeploy your application to pick up new token

# 4. Verify new token works
curl -s https://api.hubapi.com/crm/v3/objects/contacts?limit=1 \
  -H "Authorization: Bearer $HUBSPOT_ACCESS_TOKEN" | jq .status

Step 5: Git Secret Scanning

# .github/workflows/secret-scan.yml
name: Secret Scan
on: [push, pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Check for HubSpot tokens
        run: |
          if grep -rE "pat-[a-z]{2}[0-9]-[a-f0-9-]{36}" --include="*.ts" --include="*.js" --include="*.json" .; then
            echo "ERROR: HubSpot access token found in source code"
            exit 1
          fi

Output

  • Minimal scopes configured per use case
  • Tokens stored in environment variables, never in code
  • Webhook signatures verified with replay protection
  • Token rotation procedure documented
  • CI scanning for leaked tokens

Error Handling

Security IssueDetectionMitigation
Token in git historygit log -p --all -S "pat-na1"Rotate token immediately
Excessive scopesAudit in Settings > Private AppsRemove unneeded scopes
Unverified webhooksSecurity auditAdd signature verification
Token never rotatedTrack creation dateSchedule quarterly rotation

Resources

Next Steps

For production deployment, see hubspot-prod-checklist.

Info
Category Development
Name hubspot-security-basics
Version v20260423
Size 6.09KB
Updated At 2026-04-28
Language