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.md
source 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 TypeFormatUse CaseSensitivity
Private API Key
pk_*
(40+ chars)
Server-side REST APICRITICAL -- never expose client-side
Public API Key6 alphanumeric charsClient-side Track/Identify onlyLow -- 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:

EnvironmentRecommended ScopesRationale
Development
profiles:read
,
events:read
,
lists:read
Read-only exploration
Staging
profiles:read/write
,
events:write
,
lists:read/write
Full test coverage
ProductionExact scopes your app needsMinimize blast radius
CI/CD
profiles:read
,
events:read
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
  • .env
    files in
    .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 IssueDetectionMitigation
Leaked private keyGit scanning,
trufflehog
Revoke immediately, rotate
Excessive scopesScope auditReduce to minimum required
Missing webhook verificationCode reviewAdd HMAC check
Key not rotatedAge > 90 daysSchedule rotation
401s after rotationLog monitoringVerify all services updated

Resources

Next Steps

For production deployment, see

klaviyo-prod-checklist
.