This is my experience running one WooCommerce store. Some of it may apply to you, some of it may not. I'm very far from being an expert on any of this (very, very far!) just a founder who spent six weeks working out why his basket abandon flow wasn't converting. Posting in case it saves anyone else the same journey, or in case anything's useful/resonates.
TL;DR
If you're running a Basket Abandon flow on the Added to Cart metric for a WooCommerce store, three things are probably working against you:
-
Your trigger is inflated with noise. The built-in Added to Cart metric fires on email clicks, Stripe Apple Pay cart-calculations, not just real adds. The flow audience is polluted and you may be creating a doom loop where your own abandon emails re-trigger the flow.
-
Your rebuild links are restoring empty baskets. Klaviyo's plugin reads the cart session before WooCommerce has finished writing it, so an indeterminate but significant share of real Added to Cart events carry an empty CartRebuildKey. Customers click the rebuild link and land on an empty basket.
-
The default rebuild URL in Klaviyo's documentation is wrong for UK stores. Klaviyo documents
/cart/, but WooCommerce UK defaults to/basket/. Silent failure if you don't know.
Most of this isn't obvious from Klaviyo's documentation. One of the fixes is a single trigger filter that takes thirty seconds to add (ItemCount>0 on the trigger) and will help every merchant on this integration, regardless of technical depth. The rest is functions.php code if you want to go further.
Setup
Fairly vanilla. Relevant bits:
-
WooCommerce with AJAX add-to-cart enabled (the default)
-
Klaviyo's official WooCommerce plugin, up to date
-
Kinsta for hosting
-
Stripe for payments, with Apple Pay/GPay enabled (relevant to point 2 below)
-
One custom configurator for bespoke products (which caused its own minor problems, covered below)
-
CheckoutWC for checkout, side cart disabled and Elementor or page rendering. (Neither relevant to any of the issues below, but mentioning in case you're running either/both and wondering)
-
Basket Abandon flow triggered off the built-in Added to Cart metric
Flow looked fine on the surface: emails sending, opens and clicks healthy. Conversion: almost zero. Rebuild links were restoring empty baskets.
I spent six weeks diagnosing this. What follows is what we found, structured around the three components of a working Basket Abandon flow: trigger, data, delivery.
Component 1: Trigger
What should happen: A real customer clicks Add to Cart. A single event fires. They enter the flow audience.
What actually happens: The built-in Added to Cart metric fires from several sources, not just real adds.
-
Clicking a product link in any Klaviyo email fires an Added to Cart event (empty payload). This is the worst one. It means your Basket Abandon flow's own emails re-trigger the flow when customers click the rebuild link. Customers can end up in a doom loop. Profile filters partially mitigate this but the noise in your metric is real.
-
Stripe Apple Pay cart-calculation fires an Added to Cart event on every product page load (empty payload). Stripe's integration does a background cart-calculation to preview shipping and tax on the Apple Pay button. This triggers
woocommerce_add_to_cartas a side effect, which fires the Klaviyo hook. Every product page view produces at least one phantom event. -
Custom configurators and cart addons may fire Added to Cart on option changes, not just on button click (populated payload, but not a real add). If you use a configurator for custom products, check whether it fires
woocommerce_add_to_carton option changes. If yes, every option click is being recorded as an add. -
Real add-to-cart actions — which themselves have a problem, see Component 2.
Component 2: Data
What should happen: The event carries enough data to show the right product in the email and rebuild the exact cart.
What actually happens:
-
Product display data (product name, price, image, URL) was always correct. Read directly from the product object, not affected by anything else.
-
Cart rebuild data (ItemCount, Items array, GrandTotal, CartRebuildKey) was the core problem. Klaviyo's plugin reads the cart session at priority 25 on
woocommerce_add_to_cart, which runs before WooCommerce has persisted the session to the database. The plugin reads an empty cart. Every CartRebuildKey decodes to{"composite":[],"normal_products":[]}.
This is a race condition. On any given real add, the cart session may or may not have persisted in time. In our experience, on production, it usually hadn't. Product fields populate correctly (they come from the product object) but cart fields are empty.
I never got round (as it's a hard task given I can't download the relevant data cleanly) to figuring out how many real ATC events weren't being populated, or weren't, or why. But it was definitely 'significant'.
Diagnostic signature: open Klaviyo → Metrics → Added to Cart → Activity Feed → click into any recent event. If AddedItemProductName, AddedItemSKU and AddedItemPrice are populated but ItemCount, GrandTotal and CartRebuildKey are all zero or empty, this is the issue.
Component 3: Delivery
What should happen: Customer clicks the rebuild link in the email. Their cart restores.
What actually happens: Three sub-components, two of which can break things.
-
URL structure. Klaviyo's documentation specifies
/cart?wck_rebuild_cart=.... WooCommerce UK stores default to/basket/, not/cart/. If your site uses anything other than/cart/and your flow uses the default documented URL, the rebuild will silently fail. Your rebuild URL needs to match whatever cart slug your store actually uses. -
CartRebuildKey content. Broken upstream by Component 2. An empty key produces an empty basket on click, even if the URL is correct.
-
UTM parameters. If you have UTM tracking enabled at flow level, Klaviyo appends UTM parameters to the rebuild URL. In our testing this appeared to interfere with the rebuild, though we haven't fully verified whether it was the UTM parameters themselves or a symptom of the empty CartRebuildKey above. Worth testing with UTM on and off if your rebuild link isn't working.
What to do about it
Two tiers of fix. The first is a thirty-second change that helps every merchant. The second is a more substantial workaround if you want to solve the underlying problem properly.
Tier 1: Add a trigger filter (everyone should do this)
In your Basket Abandon flow trigger settings, add a filter:
ItemCount > 0
That's it. This eliminates all the empty-payload noise from email clicks, Stripe phantoms, and the timing-bug empty events. Only real adds with populated data enter the flow.
It doesn't solve the rebuild problem (empty keys are still empty) but it massively improves the quality of your flow audience. Previously broken rebuilds just won't send. Worth doing even before you read the rest.
I'm not aware of this being documented anywhere by Klaviyo, which is why I'm posting. Apologies if it's a well known best practice!
Tier 2: Custom server-side event
If you want to solve the timing issue properly and capture every real add with a populated CartRebuildKey, the solution is to fire your own event at priority 999 on woocommerce_add_to_cart (after the cart session has persisted), POST it to Klaviyo's client events endpoint, and rebuild your Basket Abandon flow to trigger off the custom event.
Code sits entirely in your theme's functions.php. No plugin edits needed. No Klaviyo configuration beyond building the new flow (Klaviyo auto-creates the custom metric the first time an event with a new metric name arrives, so nothing to configure on Klaviyo's side).
The core pattern:
add_action( 'woocommerce_add_to_cart', 'my_atc_server', 999, 6 );function my_atc_server( $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ) { // Skip Stripe cart-calculation side effects if ( isset( $_SERVER['REQUEST_URI'] ) && strpos( $_SERVER['REQUEST_URI'], 'wc_stripe_frontend_request' ) !== false ) { return; } // Cookie check if ( ! isset( $_COOKIE['__kla_id'] ) ) { return; } // Cart is now populated. Build the payload and POST to Klaviyo client endpoint. // ... (full implementation is longer — message me if you want the complete code)}Full implementation includes profile identification (reading $exchange_id, email, and cid from the Klaviyo cookie), cart data payload, CartRebuildKey building, and image/URL fallback logic. Happy to share if it's useful.
Things for Klaviyo to address
If anyone from Klaviyo sees this:
-
Fix the timing issue in the plugin. Three lines in
wck-added-to-cart.php: initialiseWC()->session, callWC()->cart->get_cart(), callcalculate_totals()beforewck_build_cart_data(). This removes the race condition and makes CartRebuildKey reliable for all merchants. -
Update the documentation.
/cart/is not universal. Mention that UK, French, Spanish and other localised WooCommerce stores use different slugs. -
Document
ItemCount > 0as a recommended trigger filter. Even without any code fix, this single filter stops email-click artifacts and phantom events from polluting Basket Abandon flows. It should be in every Basket Abandon template by default.
Acknowledgements and onward
I've discussed this with Klaviyo support. Hopefully they'll fix it all their end, but in the meantime I thought I'd break my forum cherry in case anyone else is battling the same problem.
If you've hit any of this, hope this helps.

