From hubspot-pack
Applies HubSpot security best practices: least-privilege scopes, private app token storage/validation, and v3 webhook signature verification with HMAC SHA-256.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin hubspot-packThis skill is limited to using the following tools:
Security best practices for HubSpot private app tokens, OAuth scopes, webhook signature verification, and secret management.
Secures Intercom integrations with token storage best practices, HMAC-SHA1 webhook verification in Node.js/Express, identity hashes, and minimal OAuth scopes.
Enforces HubSpot security in JS/TS projects via CI secret scanning for tokens/API keys, ESLint rules against deprecated auth, and .gitignore checks.
Secures Attio API integrations via least-privilege token scoping, platform secret management, webhook verification, and rotation procedures.
Share bugs, ideas, or general feedback.
Security best practices for HubSpot private app tokens, OAuth scopes, webhook signature verification, and secret management.
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.
# .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) + '...',
});
}
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 });
}
);
# 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
# .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
| 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 |
For production deployment, see hubspot-prod-checklist.