Claude-skill-registry klaviyo
Klaviyo email/SMS marketing - profiles, events, flows, segmentation
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/klaviyo" ~/.claude/skills/majiayu000-claude-skill-registry-klaviyo-e1caed && rm -rf "$T"
manifest:
skills/data/klaviyo/SKILL.mdsource content
Klaviyo E-Commerce Marketing Skill
Load with: base.md + (typescript.md or python.md)
For integrating Klaviyo email/SMS marketing - customer profiles, event tracking, campaigns, flows, and segmentation.
Sources: Klaviyo API Docs | API Reference
Why Klaviyo
| Feature | Benefit |
|---|---|
| E-commerce Native | Built for online stores, deep integrations |
| Event-Based | Trigger flows from any customer action |
| Segmentation | Advanced filtering on behavior + properties |
| Email + SMS | Unified platform for both channels |
| Analytics | Revenue attribution per campaign |
API Basics
Base URLs
| Type | URL |
|---|---|
| Server-side (Private) | |
| Client-side (Public) | |
Authentication
// Server-side: Private API Key const headers = { "Authorization": "Klaviyo-API-Key pk_xxxxxxxxxxxxxxxxxxxxxxxx", "Content-Type": "application/json", "revision": "2024-10-15", // API version }; // Client-side: Public API Key (6 characters) const publicKey = "XXXXXX"; // Company ID // Use as query param: ?company_id=XXXXXX
API Key Scopes
| Scope | Access |
|---|---|
| Read-only | View data only |
| Full | Read + write (default) |
| Custom | Specific permissions |
Installation
Node.js
npm install klaviyo-api
// lib/klaviyo.ts import { ApiClient, EventsApi, ProfilesApi, ListsApi } from "klaviyo-api"; const client = new ApiClient(); client.setApiKey(process.env.KLAVIYO_PRIVATE_KEY!); export const eventsApi = new EventsApi(client); export const profilesApi = new ProfilesApi(client); export const listsApi = new ListsApi(client);
Python
pip install klaviyo-api
# lib/klaviyo.py from klaviyo_api import KlaviyoAPI klaviyo = KlaviyoAPI( api_key=os.environ["KLAVIYO_PRIVATE_KEY"], max_delay=60, max_retries=3 )
Direct HTTP (Any Language)
// lib/klaviyo.ts const KLAVIYO_BASE_URL = "https://a.klaviyo.com/api"; async function klaviyoRequest( endpoint: string, method: "GET" | "POST" | "PATCH" | "DELETE" = "GET", body?: object ) { const response = await fetch(`${KLAVIYO_BASE_URL}${endpoint}`, { method, headers: { Authorization: `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_KEY}`, "Content-Type": "application/json", revision: "2024-10-15", }, body: body ? JSON.stringify(body) : undefined, }); if (!response.ok) { const error = await response.json(); throw new Error(`Klaviyo API error: ${JSON.stringify(error)}`); } return response.json(); }
Profiles (Customers)
Create/Update Profile
// Upsert profile (create or update) async function upsertProfile(data: ProfileInput) { return klaviyoRequest("/profiles", "POST", { data: { type: "profile", attributes: { email: data.email, phone_number: data.phone, // E.164 format: +1234567890 first_name: data.firstName, last_name: data.lastName, properties: { // Custom properties lifetime_value: data.ltv, plan: data.plan, signup_source: data.source, }, location: { city: data.city, region: data.state, country: data.country, zip: data.zip, }, }, }, }); }
# Python def upsert_profile(data): return klaviyo.Profiles.create_or_update_profile({ "data": { "type": "profile", "attributes": { "email": data["email"], "first_name": data["first_name"], "last_name": data["last_name"], "properties": { "plan": data.get("plan"), } } } })
Get Profile
async function getProfileByEmail(email: string) { const response = await klaviyoRequest( `/profiles?filter=equals(email,"${email}")` ); return response.data[0]; } async function getProfileById(profileId: string) { return klaviyoRequest(`/profiles/${profileId}`); }
Update Profile Properties
async function updateProfileProperties( profileId: string, properties: Record<string, any> ) { return klaviyoRequest(`/profiles/${profileId}`, "PATCH", { data: { type: "profile", id: profileId, attributes: { properties, }, }, }); } // Usage await updateProfileProperties("profile_id", { last_purchase_date: new Date().toISOString(), total_orders: 5, vip_status: true, });
Events (Tracking)
Track Event (Server-Side)
async function trackEvent(data: EventInput) { return klaviyoRequest("/events", "POST", { data: { type: "event", attributes: { profile: { data: { type: "profile", attributes: { email: data.email, // or phone_number, or external_id }, }, }, metric: { data: { type: "metric", attributes: { name: data.eventName, }, }, }, properties: data.properties, value: data.value, // For revenue tracking unique_id: data.uniqueId, // Deduplication time: data.timestamp || new Date().toISOString(), }, }, }); }
Common E-Commerce Events
// Viewed Product await trackEvent({ email: customer.email, eventName: "Viewed Product", properties: { ProductID: product.id, ProductName: product.name, ProductURL: product.url, ImageURL: product.image, Price: product.price, Categories: product.categories, }, }); // Added to Cart await trackEvent({ email: customer.email, eventName: "Added to Cart", properties: { ProductID: product.id, ProductName: product.name, Quantity: quantity, Price: product.price, CartTotal: cart.total, ItemNames: cart.items.map(i => i.name), }, value: product.price * quantity, }); // Started Checkout await trackEvent({ email: customer.email, eventName: "Started Checkout", properties: { CheckoutURL: checkout.url, ItemCount: cart.itemCount, Categories: cart.categories, ItemNames: cart.items.map(i => i.name), }, value: cart.total, }); // Placed Order await trackEvent({ email: customer.email, eventName: "Placed Order", properties: { OrderId: order.id, ItemCount: order.itemCount, Categories: order.categories, ItemNames: order.items.map(i => i.name), Items: order.items.map(i => ({ ProductID: i.productId, ProductName: i.name, Quantity: i.quantity, Price: i.price, ImageURL: i.image, ProductURL: i.url, })), BillingAddress: order.billingAddress, ShippingAddress: order.shippingAddress, }, value: order.total, uniqueId: order.id, // Prevent duplicate orders }); // Fulfilled Order await trackEvent({ email: customer.email, eventName: "Fulfilled Order", properties: { OrderId: order.id, TrackingNumber: fulfillment.trackingNumber, TrackingURL: fulfillment.trackingUrl, Carrier: fulfillment.carrier, }, }); // Cancelled Order await trackEvent({ email: customer.email, eventName: "Cancelled Order", properties: { OrderId: order.id, Reason: cancellation.reason, }, value: -order.total, // Negative value for refunds });
Client-Side Tracking (JavaScript)
<!-- Add to your site --> <script async src="https://static.klaviyo.com/onsite/js/klaviyo.js?company_id=XXXXXX"></script> <script> // Identify user klaviyo.identify({ email: "customer@example.com", first_name: "John", last_name: "Doe", }); // Track event klaviyo.track("Viewed Product", { ProductID: "prod_123", ProductName: "Blue T-Shirt", Price: 29.99, }); // Track with value klaviyo.track("Added to Cart", { ProductID: "prod_123", ProductName: "Blue T-Shirt", Price: 29.99, $value: 29.99, // Revenue tracking }); </script>
Lists & Segments
Add Profile to List
async function addToList(listId: string, emails: string[]) { return klaviyoRequest(`/lists/${listId}/relationships/profiles`, "POST", { data: emails.map(email => ({ type: "profile", attributes: { email }, })), }); } // By profile ID async function addProfileToList(listId: string, profileId: string) { return klaviyoRequest(`/lists/${listId}/relationships/profiles`, "POST", { data: [{ type: "profile", id: profileId }], }); }
Remove from List
async function removeFromList(listId: string, profileId: string) { return klaviyoRequest( `/lists/${listId}/relationships/profiles`, "DELETE", { data: [{ type: "profile", id: profileId }], } ); }
Get List Members
async function getListMembers(listId: string, cursor?: string) { const params = new URLSearchParams({ "page[size]": "100", }); if (cursor) { params.set("page[cursor]", cursor); } return klaviyoRequest(`/lists/${listId}/profiles?${params}`); }
Create List
async function createList(name: string) { return klaviyoRequest("/lists", "POST", { data: { type: "list", attributes: { name }, }, }); }
Campaigns
Get Campaigns
async function getCampaigns(status?: "draft" | "scheduled" | "sent") { const params = new URLSearchParams(); if (status) { params.set("filter", `equals(status,"${status}")`); } return klaviyoRequest(`/campaigns?${params}`); }
Get Campaign Performance
async function getCampaignMetrics(campaignId: string) { return klaviyoRequest( `/campaign-recipient-estimations/${campaignId}`, "GET" ); }
Flows (Automations)
Get Flows
async function getFlows() { return klaviyoRequest("/flows"); } async function getFlowById(flowId: string) { return klaviyoRequest(`/flows/${flowId}`); }
Common Flow Triggers
| Flow Type | Trigger Event |
|---|---|
| Welcome Series | Added to List |
| Abandoned Cart | Added to Cart + No Purchase |
| Browse Abandon | Viewed Product + No Cart |
| Post-Purchase | Placed Order |
| Winback | No Order in X Days |
| Review Request | Fulfilled Order |
Webhooks
Create Webhook
async function createWebhook(data: WebhookInput) { return klaviyoRequest("/webhooks", "POST", { data: { type: "webhook", attributes: { name: data.name, endpoint_url: data.url, secret_key: data.secret, topics: data.topics, // e.g., ["profile.created", "event.created"] }, }, }); }
Webhook Topics
| Topic | Trigger |
|---|---|
| New profile created |
| Profile properties changed |
| Profiles merged |
| New event tracked |
| Profile added to list |
| Profile removed from list |
Verify Webhook Signature
import crypto from "crypto"; function verifyKlaviyoWebhook( payload: string, signature: string, secret: string ): boolean { const expectedSignature = crypto .createHmac("sha256", secret) .update(payload) .digest("base64"); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); } // Express handler app.post("/webhooks/klaviyo", (req, res) => { const signature = req.headers["klaviyo-webhook-signature"] as string; if (!verifyKlaviyoWebhook(JSON.stringify(req.body), signature, WEBHOOK_SECRET)) { return res.status(401).json({ error: "Invalid signature" }); } const { type, data } = req.body; switch (type) { case "profile.created": handleNewProfile(data); break; case "event.created": handleNewEvent(data); break; } res.status(200).json({ received: true }); });
Rate Limits
| Window | Limit |
|---|---|
| Burst | 75 requests/second |
| Steady | 700 requests/minute |
Handle Rate Limiting
async function klaviyoRequestWithRetry( endpoint: string, method: "GET" | "POST" | "PATCH" | "DELETE" = "GET", body?: object, retries = 3 ): Promise<any> { for (let attempt = 0; attempt < retries; attempt++) { const response = await fetch(`${KLAVIYO_BASE_URL}${endpoint}`, { method, headers: { Authorization: `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_KEY}`, "Content-Type": "application/json", revision: "2024-10-15", }, body: body ? JSON.stringify(body) : undefined, }); if (response.status === 429) { const retryAfter = parseInt(response.headers.get("Retry-After") || "5"); await new Promise(r => setTimeout(r, retryAfter * 1000)); continue; } if (!response.ok) { throw new Error(`Klaviyo error: ${response.status}`); } return response.json(); } throw new Error("Max retries exceeded"); }
Pagination
async function getAllProfiles() { const profiles = []; let cursor: string | undefined; do { const params = new URLSearchParams({ "page[size]": "100" }); if (cursor) { params.set("page[cursor]", cursor); } const response = await klaviyoRequest(`/profiles?${params}`); profiles.push(...response.data); cursor = response.links?.next ? new URL(response.links.next).searchParams.get("page[cursor]") : undefined; } while (cursor); return profiles; }
Filtering & Sorting
// Filter by date const recentEvents = await klaviyoRequest( `/events?filter=greater-than(datetime,2024-01-01T00:00:00Z)` ); // Filter by property const vipProfiles = await klaviyoRequest( `/profiles?filter=equals(properties.vip_status,true)` ); // Multiple filters (AND) const filtered = await klaviyoRequest( `/profiles?filter=and(equals(properties.plan,"pro"),greater-than(properties.ltv,1000))` ); // Sorting const sorted = await klaviyoRequest( `/profiles?sort=-created` // Descending by created date ); // Sparse fieldsets (only return specific fields) const sparse = await klaviyoRequest( `/profiles?fields[profile]=email,first_name,properties` );
Integration Patterns
E-Commerce Order Sync
// After order is placed async function syncOrderToKlaviyo(order: Order) { // 1. Upsert customer profile await upsertProfile({ email: order.customerEmail, firstName: order.customerFirstName, lastName: order.customerLastName, phone: order.customerPhone, }); // 2. Update lifetime metrics await updateProfileProperties( await getProfileIdByEmail(order.customerEmail), { last_order_date: new Date().toISOString(), total_orders: order.customerOrderCount, lifetime_value: order.customerLifetimeValue, } ); // 3. Track order event await trackEvent({ email: order.customerEmail, eventName: "Placed Order", properties: { OrderId: order.id, Items: order.items, // ... other properties }, value: order.total, uniqueId: order.id, }); }
Subscription Status Sync
// When subscription changes async function syncSubscriptionStatus(user: User, status: string) { await updateProfileProperties(user.klaviyoProfileId, { subscription_status: status, subscription_plan: user.plan, subscription_updated_at: new Date().toISOString(), }); await trackEvent({ email: user.email, eventName: `Subscription ${status}`, properties: { plan: user.plan, mrr: user.mrr, }, value: status === "cancelled" ? -user.mrr : user.mrr, }); }
Environment Variables
# .env KLAVIYO_PRIVATE_KEY=pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx KLAVIYO_PUBLIC_KEY=XXXXXX KLAVIYO_WEBHOOK_SECRET=your_webhook_secret
Add to
credentials.md:
'KLAVIYO_PRIVATE_KEY': r'pk_[a-f0-9]{32}', 'KLAVIYO_PUBLIC_KEY': r'[A-Z0-9]{6}',
Checklist
Setup
- Klaviyo account created
- Private API key generated
- Public API key noted (company ID)
- API revision set in headers
Integration
- Profile sync on signup/update
- Key events tracked (view, cart, order)
- Order events include Items array
- Revenue tracked with $value
- Unique IDs for deduplication
Testing
- Test profile creation
- Test event tracking
- Verify events in Klaviyo dashboard
- Test webhook delivery
- Test rate limit handling
Anti-Patterns
- Missing email/phone - Every profile needs at least one identifier
- Duplicate events - Use unique_id for orders/transactions
- Missing Items array - Required for product recommendations
- Client-side only - Server-side tracking is more reliable
- Ignoring rate limits - Implement exponential backoff
- Hardcoded API keys - Use environment variables
- Missing revenue tracking - Include $value for ROI attribution