Docs menu · Confirming orders
Getting Started
Integrations
Server-side GTM
COD Order Protection
Advanced Features
Downloads
Tools & Support
Docs / COD Order Protection
COD Order Protection
Confirming and rejecting orders
Held orders wait until you say they are real. Approve them by hand, or let your store, CRM or courier do it.
By hand
Open COD Held Orders in the dashboard and choose Approve & Fire or Reject on each order.
Best when a person calls to verify each order.
Automatically
Your store, CRM or courier posts the order's new status to PixelFly, and the order is confirmed or rejected to match.
Best at volume. WooCommerce stores get this from the plugin — no code needed.
Confirm within 7 days where you can
Inside that window Meta records the purchase at the original order time, so the ad that produced the sale gets the credit (GA4's window is 72 hours). Later confirmations are still sent — they are simply dated when you confirmed, not when the order was placed.
Which statuses confirm, and which reject
When a status arrives by webhook, PixelFly acts on it like this:
| Result | Statuses |
|---|---|
| Confirms the purchase is sent | confirmed processing completed paid shipped order-shipped dispatched partially-shipped delivered out-for-delivery in-transit intransit picked-up ready-for-pickup |
| Rejects nothing is sent | cancelled canceled failed refunded returned trash |
| Ignored the order keeps waiting | Anything else. Deliberate: firing a purchase for an order your store never confirmed is the exact harm this feature prevents. |
Matching is forgiving about spelling: the status is lowercased, a leading wc- is stripped and underscores become hyphens. So wc-out_for_delivery, Out For Delivery and out-for-delivery are the same status. Courier plugins that add their own statuses work without extra setup.
A confirmation that arrives before the hold does is kept, and applied as soon as the hold lands. Order of arrival never loses a sale.
What cannot go wrong
- An order is never counted twice. Holds are keyed on the order ID, so a second hold for the same order updates the first instead of creating another — which is why running the browser hold and the WooCommerce server-side backup together is safe.
- A sent order stays sent. Once an order has fired or been rejected, a later hold for it is ignored outright. A replayed webhook or a customer refreshing the thank-you page cannot re-send a conversion.
- A second hold fills in gaps. If the first hold was missing a field and a later one has it, the order keeps the richer version.
- Only value and currency are required to send. Everything else — email, phone, click IDs — improves matching, but their absence never blocks the conversion.
Your monthly limit
Confirming an order sends a conversion, and that is what your plan counts. Holding is never limited — only sending.
| Plan | Confirmed COD orders sent per month |
|---|---|
| Free | 50 |
| Starter | 500 |
| Pro | 2,000 |
| Enterprise | 10,000 |
quota_exceeded. Nothing is lost and no order is rejected — but those orders sit there, so if you see approved orders that never fired, check your usage first.
The count resets each billing period. Rejected orders cost nothing.
Orders held without an order ID
If a purchase reaches PixelFly with no order ID, it is still held, under a stand-in name beginning NOID-. The sale is not lost — but your webhook cannot confirm it, because your store does not know that name. Approve these from the dashboard.
Sending an event_id with the purchase makes that name stable, so a repeated hold finds the same order rather than creating a second one. If you see NOID- orders regularly, the real fix is to send transaction_id — see the purchase event.
Setting up the webhook yourself
WooCommerce stores should use the plugin's auto-confirm instead — it does this for you. For any other system, POST to your Webhook URL with the Webhook Secret:
curl -X POST https://<your-tracking-domain>/cod/webhook \
-H "Content-Type: application/json" -H "X-Webhook-Secret: pfwh_…" \
-d '{"type":"order.status","order_id":"1042","status":"completed"}'
Send this whenever an order's status changes. order_id must match the one sent at checkout. Full field reference: API reference.
pfwh_…) here, not your API Key (pf_live_…). The API Key holds orders; the secret confirms them. Sending the wrong one returns 401.