We’re upgrading our email infrastructure — for immediate response, email andrewjgaber@gmail.com meanwhile.
Skip to main content
Part of Digital Empire
Affiliate / Other

Klaviyo Signals Purchase event (client + server-side variant)

Klaviyo · Web Pixels API replacement · confidence: high

Dies 2026-08-26

Paste-ready snippet
// 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),
//         },
//       },
//     }),
//   });
// }

Legacy pattern detected via: klaviyo\.track\(\s*['"]Purchase['"]|_learnq\.push\(\s*\[\s*['"]track['"]\s*,\s*['"]Purchase['"]|klSignals\.track\(\s*['"]Purchase['"]

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/.

Test-event verification checklist

  • $event_id and unique_id are set to Shopify's numeric order.id so Klaviyo dedupes if BOTH Variant A and Variant B accidentally run (or if a browser retry happens). Never leave $event_id blank.
  • Klaviyo's official Shopify App is NOT already installed and firing its own server-side Placed Order/Ordered Product events (duplicate-check same as the klaviyo-learnq-push-placed-order pattern) -- if the Klaviyo Shopify App is active, DO NOT add this snippet, the App already ships the equivalent Purchase event server-side.
  • Variant B (server-side) requires a private API key -- treat as SECRET, never hard-code, load from env var. Rotate immediately if committed to a repo. Fires once per Shopify webhook delivery; if you have Shopify webhook retries enabled the unique_id dedupes on Klaviyo's side.
  • Verify live against the platform's own test-event tool: https://developers.klaviyo.com/en/reference/create_client_event

Related paste-ready snippets

Klaviyo Signals migration · Legacy learnq.push + Additional Scripts loader + the new Signals Purchase replacement.