Tracking & Attribution

Why Does Meta Count Unpaid or Cancelled WooCommerce Orders as Purchases?

Meta shows more WooCommerce purchases than you were paid for? Why pending, failed and cancelled orders fire Purchase, and how to send paid orders only.

Quick answer

Because your Purchase event is triggered when the order is created or the thank-you page loads, not when the money arrives. WooCommerce creates an order, and shows the order-received page, before payment is confirmed, so pending, on-hold and failed orders that later get cancelled still send a Purchase. The fix is to send Purchase from the order's status: only when it reaches processing or completed.

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Meta counts unpaid and cancelled WooCommerce orders as purchases because the Purchase event is tied to the order being created, or to the thank-you page loading, not to the payment clearing. WooCommerce creates the order before the money is confirmed. So a pending, on-hold or failed order that is cancelled an hour later has already told Meta “someone bought.”

The gap only runs one way: Meta reports more purchases than your processor settled. Filter WooCommerce → Orders by date and the extras are there: Pending payment, Failed, Cancelled. Real orders, no money.

It inflates your reported ROAS, and it teaches Meta’s delivery system that people who abandon at payment are your buyers. Here’s why it happens, how to confirm it, and how to send a Purchase only for paid orders.

Why does Meta count orders you were never paid for?

Because most setups fire Purchase on an event that happens before payment is confirmed. A browser pixel fires when the order-received page loads, and some server-side setups fire when the order is created. WooCommerce reaches both points for orders that are still waiting on payment, so Meta receives a Purchase for an order that may never be paid.

WooCommerce’s checkout works in this order:

  1. The shopper clicks Place order.
  2. WooCommerce creates the order, usually as Pending payment.
  3. The payment gateway takes over: a card form, a redirect to PayPal or a bank, or instructions for a bank transfer.
  4. If the payment clears, the order moves to Processing (or Completed for some virtual products). If it doesn’t, the order stays pending, goes to Failed, or is later Cancelled.

A Purchase hooked to step 2, or to a thank-you page reachable at step 3, is ahead of the money. Payment is only known at step 4.

Which WooCommerce order statuses mean you were paid?

Only two: Processing and Completed. Everything else means the money isn’t in, or has gone back out.

Status What it means Should Meta get a Purchase?
Pending payment Order created, no payment received yet No
On hold Awaiting a manual payment, like a bank transfer or cheque Not yet — only once it’s marked paid
Failed The payment was declined or didn’t complete No
Processing Payment received, order not yet shipped Yes
Completed Paid and fulfilled Yes
Cancelled Cancelled by you, the shopper, or WooCommerce’s own unpaid-order timeout No
Refunded Paid, then refunded in full It was paid — see the refund section below

Where do the unpaid Purchases come from?

These are the usual patterns:

  • Pending orders that time out. WooCommerce holds stock for unpaid orders for the time set under WooCommerce → Settings → Products → Inventory (“Hold stock”), then cancels them. If the pixel fired when the order was placed, the Purchase stays in Meta after the cancellation.
  • Bank transfer, cheque and other manual methods. The order goes on hold and the thank-you page loads straight away with payment instructions. A pixel on that page counts the sale before anyone has paid, and some of those orders are never paid.
  • Failed payments that still reach a thank-you page. Some gateways send the shopper back to the order-received page whatever the result. If the pixel doesn’t check the order status, a declined card becomes a Purchase.
  • Server events on the wrong hook. A plugin that sends its server-side Purchase when the order is created has the same problem as a browser pixel — it just has it on the server. Moving to the Conversions API doesn’t fix the trigger.

Reports of this recur in WooCommerce pixel plugin support forums, the official Meta integration’s included. The trigger point is the cause, not a Meta bug.

How do you confirm unpaid orders are being counted?

Compare, for the same date range, the number of paid orders in WooCommerce with the number of Purchases Meta received. Then place one test order with a manual payment method and watch whether a Purchase arrives before you mark it paid. If it does, your trigger is ahead of payment.

  1. Count paid orders. In WooCommerce → Orders, filter to a recent week and count the orders in Processing and Completed. That’s your real purchase count.
  2. Count unpaid orders. Same week, count Pending payment, On hold, Failed and Cancelled. If Meta’s excess is close to this number, you’ve found it.
  3. Check Meta’s count. In Events Manager, look at the Purchase events received for the same days. Use Events Manager’s count of events received, not the attributed conversions in Ads Manager — those depend on attribution windows and won’t reconcile to orders.
  4. Run a live test. Enable a manual method such as bank transfer, open Events Manager’s test events tool (the same check used to confirm your Meta CAPI is working), and place an order. If a Purchase appears while the order is still on hold or pending, your setup counts unpaid orders.
  5. Match order ids. If your events send the WooCommerce order number as order_id, open a Purchase in the test tool and check its id against the order’s status. A Purchase for an order that is now Cancelled settles the question.

Before you blame the trigger, make sure only one tool sends Purchase. If two do, the excess might be duplicates rather than unpaid orders. The fix for that is covered in why Meta counts extra purchases per order when shoppers return from a payment redirect.

How do you stop unpaid orders from firing a Purchase?

Send Purchase from the order record when its status changes to Processing or Completed, and stop firing it on the thank-you page or at order creation. That moves the trigger to the only point where you know you were paid. It also means the event arrives from your server, so it doesn’t depend on the shopper’s browser at all.

In practice:

  • Check your plugin’s settings first. Some pixel plugins let you choose which order statuses count. Limit it to Processing and Completed.
  • Switch off the thank-you page Purchase. A pixel on the order-received page can’t know whether a bank transfer will arrive. Remove its Purchase, or make it conditional on payment.
  • Send Purchase server-side on the status change. A server event fired when the order moves to Processing or Completed carries the payment decision with it. This is the approach in the guide to tracking WooCommerce purchases server-side to Meta, and it runs through the Conversions API.
  • Keep one source per event. Pick one tool to own Purchase. Two tools with two different event ids will each be counted.

What about bank transfers that get paid days later?

Send the Purchase when you mark the order paid, not when it’s placed. The catch is Meta’s time limit: a server event’s event_time can’t be more than seven days old when you send it. If an order sits on hold for longer, its original timestamp falls outside the window. The 7-day event time window in Meta CAPI explains what happens and how to handle it.

Order-status gate for a WooCommerce store: six orders flowing in with statuses Pending payment, On hold, Failed, Cancelled, Processing and Completed; the four unpaid statuses stop at a grey gate marked not sent, while Processing and Completed pass through to a server-side Purchase delivered to Meta, TikTok and Pinterest, each with its own event_id

What about orders that are paid, then cancelled or refunded?

A Purchase sent for a paid order was correct when it was sent, and fixing the trigger won’t take it back. If you cancel or refund that order later, Ads Manager’s count doesn’t go down on its own. Paid-only sending removes the orders that were never paid; refunds need netting in your own revenue reporting instead.

A store with a high return rate, or one that cancels fraud-flagged orders after payment, will still see Meta’s revenue sit above what it kept.

  • Report net revenue yourself. Your ROAS decisions should run on revenue after refunds, matched to the campaign. Why refunds inflate attributed revenue covers how to net them.
  • Don’t send twice to correct it. Re-sending a Purchase with a lower value creates a second purchase in Meta’s count, not a correction.

How does PartialLeads decide which WooCommerce orders become a Purchase?

PartialLeads sends a Purchase for every paid WooCommerce order — Processing or Completed — and nothing else. The event comes from WooCommerce’s order webhooks, not the thank-you page, so pending, on-hold, failed and cancelled orders never reach Meta. An order that starts on hold is sent when you mark it paid.

Where the Purchase comes from. The PartialLeads WooCommerce plugin fires nothing on the order-received page. It forwards browse events — view product, add to cart and begin checkout — server-side to Meta, Pinterest and TikTok. The Purchase is owned by the order webhooks, including later status updates, so the order record decides. The same paid-only rule applies to the Google Ads rows written to your Google Sheet.

What it sends. One Purchase per paid order, at the full order total including tax and shipping after discounts, with hashed email and phone from the order. Each event has a deterministic event_id, so a redelivered webhook can’t fire the same order twice. If a bank-transfer order is marked paid after Meta’s seven-day window, the event time is clamped into the window rather than rejected.

Refunds. Refunds are recorded as their own rows and net against revenue in the Purchases ledger and the Attribution report. They aren’t sent to Meta, so Meta’s count still includes an order that was paid and then refunded.

Reconciling a week of WooCommerce orders against the PartialLeads Purchases ledger: the WooCommerce order list on the left shows nine orders, four of them Pending payment, On hold, Failed or Cancelled; the Purchases ledger on the right lists only the five paid orders, each with its source, session match and order total, plus a refund row netting against revenue

Where you see it working. The Purchases ledger lists every paid order with its value, source and session match. Its weekly count should equal Processing plus Completed in WooCommerce. The Meta CAPI page shows each server Purchase with its status and time, one per order.

The honest constraints. An order is recorded once: if you edit a paid order later — adding an upsell, say — the value isn’t updated or re-sent. PartialLeads’ event ids are its own and don’t deduplicate against another plugin’s browser pixel, so if Meta for WooCommerce or PixelYourSite still sends Purchase, switch theirs off or you’ll keep counting unpaid orders through them. WooCommerce Subscriptions renewals are recorded but not sent; only the first payment is. PartialLeads controls which orders are sent; how Meta attributes them is Meta’s half.

What breaks The mechanism Where you see it in the dashboard
Pending or failed orders counted as purchases Purchase sent only when the order is Processing or Completed Purchases ledger, Meta CAPI page
Thank-you page fires before payment Plugin fires nothing on order-received; Purchase comes from order webhooks Meta CAPI page, one Purchase per order
Bank transfer paid days later Status updates trigger the send; event time clamped to Meta’s 7-day window Purchases ledger, CAPI activity log
Refunds inflate revenue Refund rows net against paid revenue Purchases ledger, Attribution report
Two tools both send Purchase No cross-tool dedup — keep one source per event Leads-list API column vs Events Manager sources

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Sources


Frequently asked questions

QWhich WooCommerce order status should trigger the Meta Purchase event?
Processing or Completed. Those are the two statuses that mean the payment was received. Pending payment, On hold, Failed and Cancelled mean the money hasn't arrived, so a Purchase sent for them overstates your sales. If your plugin lets you choose statuses, pick those two and nothing else.
QWhy does my pixel fire Purchase for bank transfer orders that were never paid?
Because the thank-you page loads as soon as the order is placed, showing the bank details, and a pixel on that page can't tell whether the transfer will ever arrive. The order is on hold, not paid. Send the Purchase server-side when you mark the order as Processing instead.
QWill Meta remove a Purchase if I cancel or refund the order later?
Not on its own. Once Meta has received the Purchase, cancelling or refunding the order in WooCommerce doesn't reduce Meta's count. Sending only paid orders prevents the unpaid ones, but refunds of paid orders still need netting in your own revenue reporting. Don't send a second Purchase to correct the value; it counts as another purchase.
QDoes switching to the Conversions API stop unpaid orders being counted?
Only if the server event is triggered by the right thing. A server-side Purchase fired when the order is created counts unpaid orders exactly like a browser pixel does. What fixes it is sending on the status change to Processing or Completed, whichever channel carries the event.
QWhy does Events Manager show more purchases than WooCommerce shows paid orders?
The usual causes are unpaid orders counted at checkout, two tools both sending Purchase with different event ids, and the thank-you page firing again when a shopper reloads it. Compare Events Manager's received count with your Processing and Completed orders for the same days, then check how many orders were pending, failed or cancelled.

Find the qualified leads your forms are currently throwing away.

Install PartialLeads on one landing page, send traffic, and compare what your CRM captured against what PartialLeads recovered and qualified.