From webflow-pack
Applies Webflow API security best practices: token management, least privilege scopes, OAuth secret rotation, webhook verification, and audit logging. For securing integrations and auditing configs.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin webflow-packThis skill is limited to using the following tools:
Security best practices for Webflow Data API v2 tokens, OAuth secrets, webhook
Configures Webflow enterprise RBAC with OAuth 2.0 scopes, role mappings, per-site tokens, workspace management, and audit logging for API integrations.
Secures Attio API integrations via least-privilege token scoping, platform secret management, webhook verification, and rotation procedures.
Applies HubSpot security best practices: least-privilege scopes, private app token storage/validation, and v3 webhook signature verification with HMAC SHA-256.
Share bugs, ideas, or general feedback.
Security best practices for Webflow Data API v2 tokens, OAuth secrets, webhook verification, and access control. Covers the full lifecycle from token creation to rotation and revocation.
developers.webflow.com| Token Type | Scope | Best For |
|---|---|---|
| Workspace Token | All sites in workspace | Internal tools, scripts |
| Site Token | Single site only | Single-site integrations |
| OAuth Access Token | User-authorized scopes | Public apps, marketplace apps |
Rule: Never use a workspace token where a site token would suffice.
Only request scopes your integration actually needs:
| Operation | Minimum Scope |
|---|---|
| Read site info | sites:read |
| Publish site | sites:write |
| Read CMS content | cms:read |
| Create/update CMS items | cms:write |
| Read pages | pages:read |
| Read form submissions | forms:read |
| Read products/orders | ecommerce:read |
| Create products, fulfill orders | ecommerce:write |
// Example: Read-only integration needs only these scopes
const READ_ONLY_SCOPES = "sites:read cms:read pages:read forms:read";
// CMS sync integration
const CMS_SYNC_SCOPES = "sites:read cms:read cms:write";
// Full ecommerce integration
const ECOMMERCE_SCOPES = "sites:read ecommerce:read ecommerce:write";
# .gitignore — MANDATORY
.env
.env.local
.env.*.local
*.pem
*.key
# .env.local (never committed)
WEBFLOW_API_TOKEN=your-token-here
WEBFLOW_WEBHOOK_SECRET=your-webhook-secret
WEBFLOW_OAUTH_CLIENT_SECRET=your-oauth-secret
// Load from environment, never hardcode
import { WebflowClient } from "webflow-api";
function createClient(): WebflowClient {
const token = process.env.WEBFLOW_API_TOKEN;
if (!token) {
throw new Error(
"WEBFLOW_API_TOKEN not set. " +
"Generate at https://developers.webflow.com"
);
}
return new WebflowClient({ accessToken: token });
}
# 1. Generate new token at developers.webflow.com
# (old token remains valid until revoked)
# 2. Update secret in your deployment platform
# Vercel
vercel env rm WEBFLOW_API_TOKEN production
vercel env add WEBFLOW_API_TOKEN production
# Fly.io
fly secrets set WEBFLOW_API_TOKEN=new-token-here
# GCP Secret Manager
echo -n "new-token-here" | \
gcloud secrets versions add webflow-api-token --data-file=-
# AWS Secrets Manager
aws secretsmanager update-secret \
--secret-id webflow/api-token \
--secret-string "new-token-here"
# 3. Verify new token works
curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $NEW_TOKEN" \
https://api.webflow.com/v2/sites
# 4. Revoke old token in Webflow dashboard
# 5. Monitor for 401 errors in logs
// Use different tokens per environment
const TOKEN_MAP: Record<string, string> = {
development: "WEBFLOW_API_TOKEN_DEV",
staging: "WEBFLOW_API_TOKEN_STAGING",
production: "WEBFLOW_API_TOKEN_PROD",
};
function getToken(): string {
const env = process.env.NODE_ENV || "development";
const envVar = TOKEN_MAP[env] || TOKEN_MAP.development;
const token = process.env[envVar];
if (!token) throw new Error(`${envVar} not set for ${env} environment`);
return token;
}
Webflow webhooks include a signature header for request verification:
import crypto from "crypto";
function verifyWebhookSignature(
rawBody: Buffer | string,
signature: string,
secret: string
): boolean {
if (!signature || !secret) return false;
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
// Timing-safe comparison prevents timing attacks
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
} catch {
return false; // Length mismatch
}
}
// Express middleware
import express from "express";
app.post(
"/webhooks/webflow",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.headers["x-webflow-signature"] as string;
const secret = process.env.WEBFLOW_WEBHOOK_SECRET!;
if (!verifyWebhookSignature(req.body, signature, secret)) {
console.error("Webhook signature verification failed");
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(req.body.toString());
// Process verified event...
res.status(200).json({ received: true });
}
);
interface WebflowAuditEntry {
timestamp: string;
operation: string;
method: string;
endpoint: string;
statusCode: number;
tokenType: "workspace" | "site" | "oauth";
environment: string;
durationMs: number;
}
function logApiCall(entry: WebflowAuditEntry): void {
// Structured JSON log — ship to your logging platform
console.log(JSON.stringify({
level: entry.statusCode >= 400 ? "error" : "info",
service: "webflow-integration",
...entry,
}));
}
.env files in .gitignore| Security Issue | Detection | Mitigation |
|---|---|---|
| Token in git history | git log -p | grep WEBFLOW | Revoke token immediately, rotate |
| Excessive scopes | Review at developers.webflow.com | Generate new token with minimal scopes |
| Missing webhook verification | No signature check in code | Add verifyWebhookSignature() |
| Token never rotated | No rotation log | Schedule quarterly rotation |
For production deployment, see webflow-prod-checklist.