Skip to content

Fix #3812: Remove woocommerce_new_order hook to prevent empty Purchase event data#3861

Open
faisalahammad wants to merge 1 commit into
facebook:mainfrom
faisalahammad:fix/3812-purchase-capi-empty-content-ids
Open

Fix #3812: Remove woocommerce_new_order hook to prevent empty Purchase event data#3861
faisalahammad wants to merge 1 commit into
facebook:mainfrom
faisalahammad:fix/3812-purchase-capi-empty-content-ids

Conversation

@faisalahammad

Copy link
Copy Markdown

Summary

This PR fixes a bug where Purchase CAPI (Conversions API) events were sent with empty content_ids, content_name, and contents arrays. The root cause was the woocommerce_new_order hook firing before order items were added to the order.

Fixes #3812

Problem Description

The Purchase event was being triggered by the woocommerce_new_order hook. This hook fires immediately when an order is created, but before WooCommerce adds the order items to that order. When our inject_purchase_event() method tried to get product data using $order->get_items(), it returned an empty array.

This caused Facebook to receive Purchase events with:

  • Empty content_ids: [] instead of product IDs
  • Empty content_name: [] instead of product names
  • Empty contents: [] instead of product details

Impact

  • Facebook cannot properly deduplicate events
  • Product-level attribution is lost
  • Conversion tracking becomes inaccurate
  • Ad campaign optimization is impaired

Root Cause Analysis

WooCommerce Hook Timeline

1. woocommerce_new_order                    → Order created, NO items yet (PROBLEM)
2. [WooCommerce adds items to order]        → Product association happens here
3. woocommerce_checkout_update_order_meta   → Items ARE available (CORRECT)
4. woocommerce_thankyou                     → Thank you page (browser pixel)

Why Empty Data Occurred

// In inject_purchase_event() around line 1063
foreach ( $order->get_items() as $item ) {  // Returns empty array at woocommerce_new_order
    $product = $item->get_product();
    // This loop never executes because order has no items yet
}

// Result sent to Facebook:
$product_ids = array( array() );    // Empty!
$product_names = array();           // Empty!
$contents = array();                // Empty!

Solution

Remove the woocommerce_new_order hook (1 line removed).

The Purchase event now relies on these hooks that fire at the correct time:

  1. woocommerce_process_shop_order_meta (priority 20)

    • Fires when admin saves manual orders
    • Items already added at this point
  2. woocommerce_checkout_update_order_meta (priority 30)

    • Primary hook for checkout flow
    • Fires after order is saved AND items are added
    • Perfect timing for server-side CAPI event
  3. woocommerce_thankyou (priority 40)

    • Browser-side pixel tracking
    • Used for browser events (not CAPI)

All scenarios (checkout flow, manual admin orders, thank you page) remain fully covered by these hooks.

Code Changes

Before (Problematic)

// facebook-commerce-events-tracker.php, line 227-231
// Purchase and Subscribe events
add_action( 'woocommerce_new_order', array( $this, 'inject_purchase_event' ), 10 );              // ← REMOVED
add_action( 'woocommerce_process_shop_order_meta', array( $this, 'inject_purchase_event' ), 20 );
add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'inject_purchase_event' ), 30 );
add_action( 'woocommerce_thankyou', array( $this, 'inject_purchase_event' ), 40 );

After (Fixed)

// facebook-commerce-events-tracker.php, line 227-230
// Purchase and Subscribe events
add_action( 'woocommerce_process_shop_order_meta', array( $this, 'inject_purchase_event' ), 20 );
add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'inject_purchase_event' ), 30 );
add_action( 'woocommerce_thankyou', array( $this, 'inject_purchase_event' ), 40 );

One line removed. Simple and clean fix that addresses the root cause.

How This Resolves the Issue

  1. Correct Hook Timing: By removing woocommerce_new_order and relying on woocommerce_checkout_update_order_meta, we ensure the Purchase event fires after order items are added.

  2. Product Data Now Available: When inject_purchase_event() runs, $order->get_items() now returns the actual order items with product information.

  3. Complete Event Data: Facebook now receives Purchase events with:

    {
      "content_ids": "[\"wc_post_id_123\", \"wc_post_id_456\"]",
      "content_name": "[\"Product Name 1\", \"Product Name 2\"]",
      "contents": "[{\"id\":\"wc_post_id_123\",\"quantity\":1}, {...}]",
      "order_id": 789
    }
  4. All Scenarios Covered:

    • Standard checkout: woocommerce_checkout_update_order_meta handles it
    • Manual admin orders: woocommerce_process_shop_order_meta handles it
    • Browser pixel: woocommerce_thankyou handles it

Test Changes

Updated 3 unit tests to reflect this change:

  1. remove_purchase_hooks() helper - Removed reference to deleted hook
  2. test_inject_purchase_event_sends_capi_for_server_context() - Uses correct hook + adds product to test order
  3. test_inject_purchase_event_sends_single_capi_for_full_checkout_flow() - Uses correct hook + adds product to test order

Test Improvement Example

// Before: Order with NO products (unrealistic)
$order = wc_create_order();
$order->set_status( 'processing' );

// After: Order WITH products (like real checkout)
$product = WC_Helper_Product::create_simple_product();
$order = wc_create_order();
$order->add_product( $product, 1 );  // ← Now has items!
$order->set_status( 'processing' );

Tests now actually verify that product data is correctly extracted.

Why This Solution is Best

  1. Correct by Design: Uses WooCommerce hook that fires at the right time in the order lifecycle
  2. Minimal Code Changes: Just 1 line removed from main code
  3. Better Performance: One fewer hook call per order (less overhead)
  4. WooCommerce Best Practice: Uses the intended hook for this purpose
  5. Addresses Root Cause: Not a workaround or defensive check, fixes the actual problem

Testing Recommendations

Manual Testing (Recommended Before Deploy)

  1. Complete a test checkout with products
  2. Check WooCommerce logs for Purchase events
  3. Verify Facebook Events Manager shows complete product data
  4. Test with different payment gateways (PayPal, Stripe, etc.)
  5. Test manual order creation in WP Admin

What to Verify

✅ content_ids contains product IDs: ["wc_post_id_123"]
✅ content_name contains product names: ["Test Product"]
✅ contents contains product details: [{"id":"wc_post_id_123","quantity":1}]
✅ order_id is present
✅ Event fires at correct time (after items added)

Risk Assessment

Low Risk because:

  • Other hooks still cover all scenarios (checkout, admin orders, thank you page)
  • Deduplication logic prevents duplicate events
  • Same event_id is shared across hooks for Facebook deduplication
  • Removing problematic hook actually improves data quality

Backward Compatibility

While this technically removes a hook, it is fixing a bug. The current implementation sends incorrect data (empty arrays), so removing the problematic hook improves correctness rather than breaking functionality.

All user-facing scenarios remain fully functional:

  • ✅ Standard checkout flow works (via woocommerce_checkout_update_order_meta)
  • ✅ Manual admin orders work (via woocommerce_process_shop_order_meta)
  • ✅ Thank you page pixel works (via woocommerce_thankyou)

Related Issues

This fix complements recent work on Purchase event handling:

Checklist

  • Code follows WordPress coding standards
  • Unit tests updated and passing
  • No backward compatibility issues
  • Documentation is clear and complete
  • Commit message follows conventions
  • Ready for review and testing

Additional Notes

The fix is straightforward and surgical. By using the correct WooCommerce hook, we ensure Purchase events contain complete product information from the start, enabling Facebook to properly track conversions and optimize ad campaigns.

… Purchase event data

- Removed woocommerce_new_order hook (priority 10) which fires before order items are added
- This hook was causing empty content_ids, content_name, and contents in CAPI Purchase events
- woocommerce_checkout_update_order_meta (priority 30) now handles server-side tracking
- Updated unit tests to use correct hook and add products to orders
- Added proper cleanup for test resources (products and orders)
@meta-cla

meta-cla Bot commented Feb 12, 2026

Copy link
Copy Markdown

Hi @faisalahammad!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant