Skip to main content

Status-Driven Purchase Conversions

Record Facebook (and other platforms') purchase conversions only when WooCommerce confirms the order is in a paid state, using the platform's Conversion API as the single source of truth.

When you need this recipe

WooCommerce orders fall into three broad outcomes after checkout:

  1. Order fails immediately. Customer never reaches the thank-you page. Nothing fires. No action needed.
  2. Order is paid immediately (most Stripe/PayPal-style integrations). Browser pixel fires on the thank-you page, CAPI fires on the paid-status transition, both share the same event_id, Meta deduplicates them. This is the normal case. No action needed.
  3. Order goes to processing but may still fail or never get paid (bank transfer, invoice, BNPL, manual review). The browser pixel fires on the thank-you page and records the conversion, even if the order is later cancelled or never paid.

This recipe addresses case 3.

The Pixel Manager's default behavior tracks everything that does not immediately fail because most checkouts fall into case 2 and waiting on a paid status hurts attribution and Meta's optimizer for the common case. Use this recipe only when case 3 is frequent enough in your store to distort the numbers.

What CAPI is and is not

The Conversions API was built for tracking reliability: it sends events from the server directly to Meta to survive ad blockers, browser privacy controls, and network filters that suppress the browser pixel. In the standard setup, CAPI runs alongside the browser pixel as a backup, and Meta deduplicates the two via event_id. This recipe repurposes CAPI as the only sender for purchase events to take advantage of its built-in status-driven trigger.

7-day limitation

Meta's Conversions API only accepts events with an event_time within the last 7 days. If an order sits in processing longer than 7 days before transitioning to a paid status, its conversion is permanently lost. This is a hard limit on Meta's side.

How the Pixel Manager handles purchase events by default

For Facebook, the Pixel Manager fires the purchase event from two places:

  1. Browser pixel - fires on the order received (thank-you) page, regardless of the order's payment status.
  2. Conversion API (CAPI) - registered on woocommerce_payment_complete and on every woocommerce_order_status_{paid_status} transition (paid statuses come from wc_get_is_paid_statuses()). A meta-key guard on the order prevents double-fires across these hooks.

Both events share the same event_id (pmw_{order_id}), which is how Facebook deduplicates them.

This means CAPI is already status-driven: failed, cancelled, and other non-paid orders never reach Facebook through the server-side pipeline. The only thing that records non-paid orders as conversions is the browser pixel firing on the thank-you page.

Recipe

To record conversions strictly when the order reaches a paid state, do two things:

1. Enable Facebook CAPI

In the Pixel Manager, open Tracking Pixels → Meta and enable Conversions API. This is what makes CAPI the source that delivers the purchase event to Meta.

Enabling Facebook in the Pixel Manager also disables the tracking pixel from the Meta for WooCommerce plugin if it is installed. Catalog sync from Meta for WooCommerce is not affected.

2. Suppress the Facebook browser pixel for purchase events

Add a JavaScript filter that returns null for purchase events on the Facebook pixel. Returning null from a pmw_pixel_data_{pixel} filter blocks that pixel from firing for the matched event.

/wp-content/themes/child-theme/functions.php
add_action('wp_head', function() {
?>
<script>
window._pmwq = window._pmwq || [];
window._pmwq.push(function() {
pmw.hooks.addFilter(
'pmw_pixel_data_facebook',
'my-store/status-driven-purchase',
function(pixelData, eventName) {
if (eventName === 'purchase') {
return null;
}
return pixelData;
}
);
});
</script>
<?php
}, 1);

With the browser purchase event suppressed, CAPI is the only sender, dedup is no longer relevant, and conversions appear in Meta when WooCommerce transitions the order into a paid state. For most payment gateways this is essentially immediate; for offline or manually approved payments, the conversion is recorded the moment the order moves into a paid status.

Optional: stricter "paid" definition (server-side filter)

The Pixel Manager already gates CAPI on wc_get_is_paid_statuses(), which by default includes processing and completed. If your store needs a narrower definition (for example: only completed, never processing), use the server-side filter for the Facebook purchase event.

The order ID is encoded in event_id as pmw_{order_id}. Parse it and load the order:

/wp-content/themes/child-theme/functions.php
add_filter('pmw_server_event_payload_facebook_purchase', function($pixel_data, $pixel_name, $event_name) {

if (empty($pixel_data['event_id'])) {
return $pixel_data;
}

$order_id = (int) str_replace('pmw_', '', $pixel_data['event_id']);
$order = wc_get_order($order_id);

if (!$order) {
return $pixel_data;
}

// Custom rule: only completed orders count as paid.
if ($order->get_status() !== 'completed') {
return null;
}

return $pixel_data;
}, 10, 3);

Returning null from this filter blocks the CAPI event for the matched pixel and event.

Custom order statuses (e.g. partially-paid)

Deposit and partial-payment plugins add custom order statuses that WooCommerce does not treat as paid. Because the purchase conversion fires on the transition into a paid status, an order that only ever reaches a custom order status such as partially-paid never triggers purchase conversion tracking.

To register a custom order status as paid, use WooCommerce's woocommerce_order_is_paid_statuses filter. It is the filter behind wc_get_is_paid_statuses(), so the Pixel Manager picks it up automatically and fires the purchase conversion when an order transitions into that status:

/wp-content/themes/child-theme/functions.php
add_filter('woocommerce_order_is_paid_statuses', function($statuses) {
$statuses[] = 'partially-paid'; // status slug without the `wc-` prefix
return $statuses;
});

There is no separate Pixel Manager filter for this; the WooCommerce filter is the single source of truth. Be aware that it widens what WooCommerce itself considers paid (for example $order->is_paid()), not just the Pixel Manager's conversion trigger.

Optional: manually re-fire CAPI for an order

If a custom workflow needs to push a CAPI purchase event for a specific order (for example from a custom hook), call the platform's static send_purchase_hit() method with the order object:

\SweetCode\Pixel_Manager\Pixels\Facebook\Facebook_CAPI::send_purchase_hit($order);

This is an internal method and is not part of the public Pixel Manager API. It can change between releases.

Same pattern for other platforms

The same approach works for every platform that has a server-side counterpart in the Pixel Manager. Replace the pixel slug in both filter names.

PlatformJavaScript filterPHP server-side filter
Facebook / Metapmw_pixel_data_facebookpmw_server_event_payload_facebook_purchase
TikTokpmw_pixel_data_tiktokpmw_server_event_payload_tiktok_purchase
Pinterestpmw_pixel_data_pinterestpmw_server_event_payload_pinterest_purchase
Snapchatpmw_pixel_data_snapchatpmw_server_event_payload_snapchat_purchase
Redditpmw_pixel_data_redditpmw_server_event_payload_reddit_purchase
Google Analytics 4pmw_pixel_data_google_analyticspmw_server_event_payload_google_analytics_purchase

Platforms without a server-side counterpart (Google Ads, Microsoft Advertising, and others)

This recipe relies on the platform having a server-side purchase API in the Pixel Manager, because that is what supplies the status-driven trigger. Google Ads does not, and neither do Microsoft Advertising, LinkedIn, X, Taboola, Outbrain, AdRoll, Criteo, and the other browser-only pixels. For those platforms the purchase conversion is sent from the browser, so suppressing the browser event with no server-side sender behind it discards the conversion entirely.

Use the pmw_conversion_prevention PHP filter instead. It withholds the browser purchase conversion for an individual order while the order is not yet in a paid status, and because the filter is re-evaluated by Automatic Conversion Recovery (pro), the conversion is recovered in full on the customer's next visit to the shop once the order has been paid.

Two differences from the CAPI-based recipe above are worth knowing before you choose it:

  • It suppresses every browser purchase pixel for the affected orders, not one platform, because the order data is withheld from the data layer as a whole.
  • Recovery requires the customer to return to the shop with the same browser. CAPI needs no return visit.

If a platform in your setup does have a Conversion API, prefer this recipe for that platform and use the filter only for the browser-only pixels.

Troubleshooting

  • Conversions still appear for failed orders. The browser pixel filter is not active. Confirm the snippet is rendered in the page source and that pmw.hooks is defined when it runs (this is what the _pmwq queue guarantees).
  • No conversions appear at all. Confirm Facebook CAPI is enabled in the Pixel Manager under Tracking Pixels → Meta and that the access token is valid. Check the logs in the Pixel Manager under Support → Logger for the CAPI request.
  • Conversions appear later than expected. That is the expected behavior. CAPI fires when WooCommerce transitions the order into a paid status, not when the customer lands on the thank-you page.

Make more money from your ads with high-precision tracking