Klaviyo · Web Pixels API replacement · confidence: high
Dies 2026-08-26
// Wave4-Bravo6 (2026-08-20): Klaviyo Signals 'Purchase' event replacement.
// Signals' onsite JS bundle (klaviyo.js / static.klaviyo.com scripts pushed
// through Additional Scripts) stops running Aug 26 when Shopify removes
// legacy checkout.liquid execution. The paste-ready replacement is the
// Klaviyo Client Events API 'Purchase' metric, fired from a Web Pixels
// analytics.subscribe('checkout_completed') block. Two variants below --
// pick ONE, not both.
//
// ============================================================
// VARIANT A -- BROWSER-SIDE (Web Pixels sandbox, no backend).
// Use if: no server capable of proxying Klaviyo Server Events API.
// Trade-off: KLAVIYO_PUBLIC_API_KEY is embedded in pixel source and
// visible to anyone who inspects the storefront -- Klaviyo public keys
// are LOW-privilege (read-limited, per Klaviyo docs), so this is the
// documented client-key path, not a token leak.
// ============================================================
analytics.subscribe('checkout_completed', (event) => {
const { checkout } = event.data;
if (!checkout || !checkout.email || !checkout.totalPrice) return;
fetch('https://a.klaviyo.com/client/events/?company_id=KLAVIYO_PUBLIC_API_KEY', {
method: 'POST',
headers: { 'Content-Type': 'application/json', revision: '2024-10-15' },
keepalive: true,
body: JSON.stringify({
data: {
type: 'event',
attributes: {
properties: {
$event_id: checkout.order ? String(checkout.order.id) : checkout.token,
$value: Number(checkout.totalPrice.amount),
Currency: checkout.totalPrice.currencyCode,
OrderId: checkout.order ? checkout.order.id : checkout.token,
Categories: (checkout.lineItems || []).map((i) => (i.variant && i.variant.product ? i.variant.product.type : null)).filter(Boolean),
ItemNames: (checkout.lineItems || []).map((i) => i.title),
Items: (checkout.lineItems || []).map((i) => ({
ProductID: i.variant ? i.variant.id : null,
SKU: i.variant ? i.variant.sku : null,
ProductName: i.title,
Quantity: i.quantity,
ItemPrice: i.variant && i.variant.price ? Number(i.variant.price.amount) : null,
RowTotal: i.variant && i.variant.price ? Number(i.variant.price.amount) * i.quantity : null,
})),
},
metric: { data: { type: 'metric', attributes: { name: 'Purchase' } } },
profile: { data: { type: 'profile', attributes: { email: checkout.email } } },
time: new Date().toISOString(),
unique_id: checkout.order ? String(checkout.order.id) : checkout.token,
},
},
}),
});
});
// ============================================================
// VARIANT B -- SERVER-SIDE (Klaviyo Server Events API).
// Use if: you have any backend that receives Shopify's order/create webhook
// (a Cloudflare Worker, a small Node handler, a Shopify App). Recommended
// over Variant A because (a) private KLAVIYO_PRIVATE_API_KEY never touches
// the browser, (b) ad-blockers cannot drop the event, (c) fires exactly
// once per real order (Shopify webhook = single source of truth).
//
// Put this in your Node/Worker handler, NOT in Web Pixels sandbox.
// ============================================================
//
// // Shopify webhook: orders/create OR orders/paid
// // Docs: https://shopify.dev/docs/api/admin-rest/current/resources/webhook
// export async function handleShopifyOrderCreated(order) {
// await fetch('https://a.klaviyo.com/api/events/', {
// method: 'POST',
// headers: {
// 'Authorization': `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_API_KEY}`,
// 'Content-Type': 'application/json',
// 'revision': '2024-10-15',
// },
// body: JSON.stringify({
// data: {
// type: 'event',
// attributes: {
// properties: {
// $event_id: String(order.id),
// $value: Number(order.total_price),
// Currency: order.currency,
// OrderId: order.id,
// Items: order.line_items.map((li) => ({
// ProductID: li.product_id,
// SKU: li.sku,
// ProductName: li.title,
// Quantity: li.quantity,
// ItemPrice: Number(li.price),
// RowTotal: Number(li.price) * li.quantity,
// })),
// },
// metric: { data: { type: 'metric', attributes: { name: 'Purchase' } } },
// profile: { data: { type: 'profile', attributes: { email: order.email } } },
// time: order.created_at,
// unique_id: String(order.id),
// },
// },
// }),
// });
// }What this does NOT cover
What this does NOT cover: Klaviyo's Started Checkout / Viewed Product / Added to Cart onsite behavioral events (those need their own analytics.subscribe blocks for checkout_started / product_viewed / product_added_to_cart). It also does not cover Klaviyo SMS Consent capture at checkout -- that requires a separate Klaviyo profile subscription-status update via /client/subscriptions/.
Klaviyo Signals migration · Legacy learnq.push + Additional Scripts loader + the new Signals Purchase replacement.