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 Case Required Scopes
Read contacts crm.objects.contacts.read
Write contacts crm.objects.contacts.read, crm.objects.contacts.write
Read/write deals crm.objects.deals.read, crm.objects.deals.write
Marketing emails content
Forms forms
Contact lists crm.lists.read, crm.lists.write
Properties crm.schemas.contacts.read
Custom objects crm.objects.custom.read, crm.objects.custom.write, crm.schemas.custom.read
Webhooks automation

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 Issue Detection Mitigation
Token in git history git log -p --all -S "pat-na1" Rotate token immediately
Excessive scopes Audit in Settings > Private Apps Remove unneeded scopes
Unverified webhooks Security audit Add signature verification
Token never rotated Track creation date Schedule 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