Docs menu · Full walkthrough

Docs / COD Order Protection

COD Order Protection · Beta

COD offline conversions using server GTM

Hold Cash on Delivery (COD) purchases at checkout, verify the order in WooCommerce or your dashboard, then fire a real Purchase to Meta Conversions API, Google Ads, and GA4 through your server-side GTM container — backdated to the original event_time with email, phone, fbp, and gclid preserved.

Similar to Stape's offline conversion guide, but built into PixelFly with a held-events dashboard, monthly fire quota, and first-party /cod/hold endpoints. For CRM / order-status webhooks → Meta CAPI, see the CRM webhook guide. Google Ads Sheet import vs hold & fire: comparison guide. To restore missing email, phone, and fbp on fire, see EMQ Enricher.

What are COD offline conversions?

A COD order is placed online, but payment happens later — often days after delivery. If you fire Purchase at checkout, Meta and Google optimize for people who place orders, including fakes and cancellations (30–50% in many COD markets).

Offline / delayed conversion tracking waits until the order is confirmed, then sends the conversion with the original checkout timestamp. Meta matches using hashed email, phone, _fbp, _fbc, and click IDs captured at checkout.

Why use PixelFly COD Order Protection?

  • Hold at checkout — your GTM (sGTM or a proxy web container) sends COD orders to {your-domain}/cod/hold; nothing hits ad platforms yet.
  • Dashboard review — approve, reject, or auto-confirm via WooCommerce webhook.
  • Fire through sGTM or Receivers — confirmed orders POST to /data → Data Client → your Meta / GA4 / Google Ads / TikTok tags, or go through PixelFly Receivers (proxy containers).
  • Prepaid unchanged — non-COD orders fire immediately (browser + server), not stored in held events.
  • First-party endpoints — same custom domain as your sGTM loader (e.g. server.yourstore.com).

Architecture

sGTM store
  Checkout (COD) → web GTM: purchase + payment_method + cod_hold=true → sGTM
  → sGTM: PixelFly COD Protection tag → POST /cod/hold → PixelFly (pending)
  Confirmed → PixelFly POSTs the purchase to {host}/data (original order time)
  → Data Client → Facebook CAPI / GA4 / Google Ads / TikTok tags

Proxy store (no sGTM)
  Checkout (COD) → web GTM: PixelFly COD Protection (Web) tag → /cod/hold → PixelFly (pending)
  Confirmed → PixelFly → Worker → your Receivers (Meta, GA4, TikTok, Google Ads)
No Purchase at checkout for COD: every tag that sends a purchase (browser Pixel, Google Ads, TikTok, and the server or PixelFly tags) gets an exception for held COD orders. Our ready-made containers already do this.

Step 1 — Enable COD Order Protection in PixelFly

  1. Open your container in the PixelFly dashboard → Power-ups → enable COD Order Protection. Do this before switching COD on in GTM.
  2. Fire path (sGTM containers): sGTM /data (recommended: your server tags, including Google Ads) or Receivers. Proxy containers always use Receivers: add one per platform (Meta, GA4, TikTok, Google Ads).
  3. Copy the Hold URL, Webhook URL and Webhook secret, and your container’s API Key (pf_live_…, on the container page).

Step 2 — Fastest: our ready-made containers

sGTM: import our web and server containers. In the server container set PixelFly - COD Host (the Hold URL without /cod/hold) and PixelFly - COD API Key, plus your Meta and GA4 IDs; in the web container set PixelFly - COD Protection = on. Publish server, then web.

Proxy: import our proxy web container, fill PixelFly API Key, Meta Pixel ID-000 and GA4-FM ID, set PixelFly - GA4 purchase via = pixelfly (with a GA4 Receiver) and PixelFly - COD Protection = on.

Both hold nothing until the API key (and, on sGTM, the host) are filled in, so a half-finished setup never loses orders. Every variable is explained in the docs.

Step 3 — WooCommerce plugin

  1. Update to PixelFly plugin v1.3.5+.
  2. PixelFly → COD Order Protection: on, mode GTM / sGTM (recommended), your COD payment methods. Keep it on whenever COD is on in GTM.
  3. With a PixelFly GTM container, leave the plugin’s API Key empty: the container sends the server-side events.
  4. Optional Auto-confirm webhook: paste the webhook secret and tick Completed and Cancelled. WooCommerce sets COD orders to Processing while they’re placed, so Processing only counts when your team moves an order there later.

Step 4 — Using your own containers

Server GTM (sGTM):

  1. Templates → Tag Templates → New → Import the sGTM COD template.
  2. Tag PixelFly COD Protection (hold): host = Hold URL host, API key = container key, order fields = Auto. Trigger: Client Name = GA4, Event Name = purchase, Event Data cod_hold = true (or payment_method = cod).
  3. Every purchase tag (Meta CAPI, GA4, Google Ads, TikTok): add that trigger as an exception.
  4. Install the Stape Data Client (path /data) and add a trigger Client Name = Data Client, Event Name = purchase, is_delayed = true to those tags: that’s the confirmed order.
  5. Check Transformations: an “Exclude parameters” transformation that doesn’t list the hold tag strips transaction_id from it. Preview’s Event Data shows the event before Transformations.

Web GTM for sGTM: send payment_method, cod_hold and gclid with the GA4 purchase, and give the browser Pixel / Google Ads / TikTok purchase tags an exception for COD.

Proxy web GTM: import the web COD template, fire it on your COD purchase with host https://track.pixelfly.io (or your tracking domain) and your API key, and give every purchase tag, including the PixelFly server-side tag, an exception for COD.

Step 5 — Confirm orders

  • COD Held Orders → Approve & Fire or Reject.
  • Or POST {"type":"order.status","order_id":"…","status":"completed"} to the Webhook URL with header X-Webhook-Secret: confirmed / processing / completed / paid / shipped confirm; cancelled / failed / refunded reject.
  • Confirm within 7 days for Meta to keep the original order time (GA4: 72 hours).

Step 6 — Google Ads

  • sGTM: the Google Ads Conversion (COD confirmed) tag in our server container (paused until you add your conversion ID and label).
  • Proxy: link GA4 to Google Ads and import the GA4 purchase, or add a Google Ads (click conversions) Receiver (Google Ads API). One of the two, not both.
  • Either way the ad click (gclid) must be captured at checkout. See Google Ads and COD.

Step 7 — sGTM Preview header (debug /data)

  1. Server GTM Preview → ⋮ → Send requests manually → copy X-Gtm-Server-Preview.
  2. PixelFly container → sGTM Preview header → paste → Update & enable.
  3. Disable it after testing so live traffic isn’t sent to Preview.

Step 8 — Test end-to-end

  1. Prepaid order: purchase tags fire as usual.
  2. COD order: only PixelFly COD Protection (hold) fires, and the order appears in COD Held Orders as pending with value, products and customer.
  3. Approve & Fire: sGTM Preview shows the Data Client purchase firing your tags (or the order lists the Receivers that accepted it).
  4. Meta Events Manager → Test Events: one Purchase from the server with the right value. Remove the test code before going live.

Improve Event Match Quality (EMQ)

Capture identifiers at hold time (not only at fire). To restore missing fbp / email / phone on the fire itself, enable EMQ Enricher (Pro+) — it fills empty CIP on POST /data without replaying PageViews.

Store at hold:

  • Hashed-ready email and phone from checkout
  • _fbp and _fbc (first-party cookies)
  • gclid for Google Ads click attribution
  • Client IP and user agent (sGTM receives these automatically)

The PixelFly plugin and GTM templates pass these fields; the held row stores them encrypted until fire.

FAQs

Is this the same as Facebook offline conversions?

Same goal — send conversions after the real sale — but COD orders originate on your website. Use action_source: website and the original checkout event_time, not offline CSV uploads.

Do prepaid orders go into Held Events?

No. Only COD is held. Prepaid fires immediately via normal GTM tags to avoid database load at scale.

Can I auto-fire on WooCommerce status change?

Yes. Enable the plugin’s auto-confirm webhook (Completed confirms, Cancelled rejects), or POST to /cod/webhook with your webhook secret from your own system.

What about legacy “Delayed Purchase Events” in the plugin?

Still available as Legacy mode for existing stores. New setups should use GTM / sGTM mode and this guide.

We use cookies to enhance your experience, analyze site traffic, and for marketing purposes. By continuing to use our site, you consent to our use of cookies. Learn more