Claude-code-plugins-plus klaviyo-security-basics
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/klaviyo-pack/skills/klaviyo-security-basics" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-klaviyo-security-basics && rm -rf "$T"
manifest:
plugins/saas-packs/klaviyo-pack/skills/klaviyo-security-basics/SKILL.mdsource content
Klaviyo Security Basics
Overview
Security best practices for Klaviyo: API key types, OAuth scopes, webhook HMAC-SHA256 signature verification, and secret rotation procedures.
Prerequisites
- Klaviyo account with API key access
- Understanding of environment variables and secret management
- Access to Klaviyo dashboard (Settings > API Keys)
Instructions
Step 1: Understand Key Types
| Key Type | Format | Use Case | Sensitivity |
|---|---|---|---|
| Private API Key | (40+ chars) | Server-side REST API | CRITICAL -- never expose client-side |
| Public API Key | 6 alphanumeric chars | Client-side Track/Identify only | Low -- safe in browser JS |
Private keys authenticate via
Authorization: Klaviyo-API-Key pk_*** header. Public keys pass as company_id query parameter.
Step 2: Environment Variable Configuration
# .env (NEVER commit) KLAVIYO_PRIVATE_KEY=pk_*************************************** KLAVIYO_PUBLIC_KEY=UXxxXx KLAVIYO_WEBHOOK_SIGNING_SECRET=whsec_************************* # .gitignore -- mandatory entries .env .env.local .env.*.local
// src/config/klaviyo.ts -- validated config loader function requireEnv(name: string): string { const value = process.env[name]; if (!value) throw new Error(`Missing required env: ${name}`); return value; } export const klaviyoConfig = { privateKey: requireEnv('KLAVIYO_PRIVATE_KEY'), publicKey: process.env.KLAVIYO_PUBLIC_KEY || '', webhookSecret: process.env.KLAVIYO_WEBHOOK_SIGNING_SECRET || '', };
Step 3: Least-Privilege API Key Scopes
Create separate API keys per environment with minimal scopes:
| Environment | Recommended Scopes | Rationale |
|---|---|---|
| Development | , , | Read-only exploration |
| Staging | , , | Full test coverage |
| Production | Exact scopes your app needs | Minimize blast radius |
| CI/CD | , | Smoke tests only |
# Use separate env vars per environment KLAVIYO_PRIVATE_KEY_DEV=pk_dev_*** KLAVIYO_PRIVATE_KEY_STAGING=pk_staging_*** KLAVIYO_PRIVATE_KEY_PROD=pk_prod_***
Step 4: Webhook Signature Verification (HMAC-SHA256)
Klaviyo signs webhook payloads using HMAC-SHA256 with your webhook signing secret.
// src/klaviyo/webhook-verify.ts import crypto from 'crypto'; /** * Verify Klaviyo webhook signature. * Klaviyo uses the webhook signing secret (set when creating the webhook) * to compute an HMAC-SHA256 signature of the payload. */ export function verifyKlaviyoWebhookSignature( payload: Buffer | string, signature: string, secret: string ): boolean { if (!signature || !secret) return false; const expectedSignature = crypto .createHmac('sha256', secret) .update(typeof payload === 'string' ? payload : payload.toString()) .digest('base64'); // Timing-safe comparison to prevent timing attacks try { return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); } catch { return false; // Different lengths } }
Step 5: Express Webhook Middleware
import express from 'express'; app.post('/webhooks/klaviyo', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['klaviyo-webhook-signature'] as string; if (!verifyKlaviyoWebhookSignature( req.body, signature, process.env.KLAVIYO_WEBHOOK_SIGNING_SECRET! )) { console.warn('[Security] Invalid webhook signature rejected'); return res.status(401).json({ error: 'Invalid signature' }); } const event = JSON.parse(req.body.toString()); // Process verified event... res.status(200).json({ received: true }); } );
Step 6: API Key Rotation Procedure
# 1. Generate new key in Klaviyo dashboard (Settings > API Keys) # - Name it with date: "Production API Key 2025-03" # - Assign same scopes as the old key # 2. Deploy new key (zero-downtime) # Update secret in your deployment platform: # - Vercel: vercel env add KLAVIYO_PRIVATE_KEY production # - AWS: aws secretsmanager update-secret --secret-id klaviyo-key --secret-string pk_new_*** # - GCP: echo -n "pk_new_***" | gcloud secrets versions add klaviyo-key --data-file=- # 3. Verify new key works curl -s -w "%{http_code}" -o /dev/null \ -H "Authorization: Klaviyo-API-Key pk_new_***" \ -H "revision: 2024-10-15" \ "https://a.klaviyo.com/api/accounts/" # 4. Revoke old key in Klaviyo dashboard # Settings > API Keys > Delete old key # 5. Audit: check logs for any 401s after rotation
Security Checklist
- Private API keys stored in environment variables / secret manager
-
files in.env.gitignore - Different API keys per environment (dev/staging/prod)
- Minimal scopes per environment
- Webhook signatures verified with HMAC-SHA256
- API key rotation scheduled (quarterly recommended)
- No private keys in client-side code
- CI/CD uses read-only key for tests
- Git history scanned for leaked keys (
)git log -p | grep pk_
Error Handling
| Security Issue | Detection | Mitigation |
|---|---|---|
| Leaked private key | Git scanning, | Revoke immediately, rotate |
| Excessive scopes | Scope audit | Reduce to minimum required |
| Missing webhook verification | Code review | Add HMAC check |
| Key not rotated | Age > 90 days | Schedule rotation |
| 401s after rotation | Log monitoring | Verify all services updated |
Resources
Next Steps
For production deployment, see
klaviyo-prod-checklist.