klaviyo について
Klaviyoのメール・SMSマーケティングAPIを統合する開発向けスキルです。プロフィール、イベント、リスト、キャンペーン、フロー、Webhookの例を含み、ECの顧客データや行動をKlaviyoに連携する開発者を対象としています。
主な機能
- 01プロフィールの作成と更新
- 02顧客イベントの追跡
- 03リストとセグメントの操作
- 04キャンペーンとフローの取得
- 05Webhookの設定と検証
- 06ページネーションとレート制限への対応
活用シーン
- 01顧客プロフィールと注文行動の同期
- 02顧客のマーケティングリストへの追加・削除
- 03キャンペーンとフローのデータ確認
- 04プロフィールまたはイベントのWebhook受信と検証
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ alinaqi/ maggy --skill klaviyoインストール後、Claude Code で/klaviyoを入力して読み込まれたか確認します。
詳細
Klaviyo Eコマースマーケティングスキル
Klaviyoのメール/SMSマーケティングを統合するためのものです。顧客プロフィール、イベントトラッキング、キャンペーン、フロー、セグメンテーションに対応します。
ソース: Klaviyo API ドキュメント | API リファレンス
Klaviyoを選ぶ理由
| 機能 | メリット |
|---|---|
| Eコマースに特化 | オンラインストア向けに構築され、深く統合されています |
| イベントベース | あらゆる顧客アクションをきっかけにフローを実行できます |
| セグメンテーション | 行動とプロパティに基づく高度なフィルタリングが可能です |
| メール+SMS | 両方のチャネルを統合したプラットフォームです |
| 分析 | キャンペーンごとの売上貢献を測定できます |
APIの基本
ベース URL
| 種類 | URL |
|---|---|
| サーバーサイド(プライベート) | https://a.klaviyo.com/api |
| クライアントサイド(パブリック) | https://a.klaviyo.com/client |
認証
// 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 キーのスコープ
| スコープ | アクセス権 |
|---|---|
| 読み取り専用 | データの閲覧のみ |
| フル | 読み取り+書き込み(デフォルト) |
| カスタム | 特定の権限 |
インストール
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
)
直接 HTTP(任意の言語)
// 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();
}
プロフィール(顧客)
プロフィールの作成/更新
// 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"),
}
}
}
})
プロフィールの取得
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}`);
}
プロフィールのプロパティの更新
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,
});
イベント(トラッキング)
イベントのトラッキング(サーバーサイド)
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(),
},
},
});
}
よく使われるEコマースイベント
// 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
});
クライアントサイドトラッキング(JavaScript)
<script async src="https://static.klaviyo.com/onsite/js/klaviyo.js?company_id=XXXXXX"></script>
<script>
// Identify user
klaviyo.identify({
email: "[email protected]",
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>
リストとセグメント
プロフィールをリストに追加
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 }],
});
}
リストから削除
async function removeFromList(listId: string, profileId: string) {
return klaviyoRequest(
`/lists/${listId}/relationships/profiles`,
"DELETE",
{
data: [{ type: "profile", id: profileId }],
}
);
}
リストメンバーの取得
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}`);
}
リストの作成
async function createList(name: string) {
return klaviyoRequest("/lists", "POST", {
data: {
type: "list",
attributes: { name },
},
});
}
キャンペーン
キャンペーンの取得
async function getCampaigns(status?: "draft" | "scheduled" | "sent") {
const params = new URLSearchParams();
if (status) {
params.set("filter", `equals(status,"${status}")`);
}
return klaviyoRequest(`/campaigns?${params}`);
}
キャンペーンのパフォーマンスを取得
async function getCampaignMetrics(campaignId: string) {
return klaviyoRequest(
`/campaign-recipient-estimations/${campaignId}`,
"GET"
);
}
フロー(自動化)
フローの取得
async function getFlows() {
return klaviyoRequest("/flows");
}
async function getFlowById(flowId: string) {
return klaviyoRequest(`/flows/${flowId}`);
}
よく使われるフローのトリガー
| フローの種類 | トリガーイベント |
|---|---|
| ウェルカムシリーズ | リストへの追加 |
| カート放棄 | カートへの追加+購入なし |
| 閲覧後の離脱 | 商品の閲覧+カートへの追加なし |
| 購入後 | 注文の確定 |
| 再購入促進 | X日間注文なし |
| レビュー依頼 | 注文のフルフィルメント完了 |
Webhook
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トピック
| トピック | トリガー |
|---|---|
profile.created | 新しいプロフィールが作成された |
profile.updated | プロフィールのプロパティが変更された |
profile.merged | プロフィールが統合された |
event.created | 新しいイベントがトラッキングされた |
list.member.added | プロフィールがリストに追加された |
list.member.removed | プロフィールがリストから削除された |
Webhook署名の検証
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 });
});
レート制限
| 期間 | 上限 |
|---|---|
| バースト | 75リクエスト/秒 |
| 定常時 | 700リクエスト/分 |
レート制限への対処
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");
}
ページネーション
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;
}
フィルタリングと並べ替え
// 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`
);
統合パターン
Eコマース注文の同期
// 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,
});
}
サブスクリプションステータスの同期
// 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,
});
}
環境変数
# .env
KLAVIYO_PRIVATE_KEY=pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
KLAVIYO_PUBLIC_KEY=XXXXXX
KLAVIYO_WEBHOOK_SECRET=your_webhook_secret
credentials.mdに追加します:
'KLAVIYO_PRIVATE_KEY': r'pk_[a-f0-9]{32}',
'KLAVIYO_PUBLIC_KEY': r'[A-Z0-9]{6}',
チェックリスト
セットアップ
- Klaviyoアカウントを作成済み
- プライベート API キーを生成済み
- パブリック API キー(会社 ID)を確認済み
- ヘッダーに API リビジョンを設定済み
統合
- 登録/更新時にプロフィールを同期
- 主要イベント(閲覧、カート、注文)をトラッキング
- 注文イベントに Items 配列を含める
- $value で売上をトラッキング
- 重複排除用の一意の ID を使用
テスト
- プロフィール作成をテスト
- イベントトラッキングをテスト
- Klaviyo ダッシュボードでイベントを確認
- Webhook 配信をテスト
- レート制限への対処をテスト
避けるべきパターン
- メールアドレス/電話番号がない - すべてのプロフィールに少なくとも1つの識別子が必要です
- イベントの重複 - 注文/取引には unique_id を使用します
- Items 配列がない - 商品レコメンデーションに必須です
- クライアントサイドのみ - サーバーサイドのトラッキングのほうが信頼性が高くなります
- レート制限を無視する - 指数バックオフを実装します
- API キーをハードコードする - 環境変数を使用します
- 売上トラッキングがない - ROI の貢献分析に $value を含めます
コメント
Markdown 対応