Docs menu · Your own sGTM containers

Docs / COD Order Protection / Set up GTM

COD Order Protection

Your own sGTM containers

Add COD Order Protection to web and server containers you already run, without replacing them.

Before you start

  • COD Order Protection is on in PixelFly, with a fire path chosen — Step 1.
  • Have ready: your API Key (pf_live_…) and your Hold URL.
  • Publishing rights on both the web and the server container.

Shortcut worth taking: import our web and server containers into a spare workspace. Every piece below is already built there, so you can open each one and copy its settings instead of typing them.

What you are building, in plain terms

Three jobs. Everything below is one of these:

  1. Hold — when a COD purchase arrives, send it to PixelFly instead of your ad platforms.
  2. Block — stop your normal purchase tags from firing for that order.
  3. Return — days later, when you confirm the order, let it come back in and fire those same tags.

Job 2 is the one people get wrong: miss a single tag and that platform still counts unconfirmed COD orders, which defeats the whole setup.

Server container

1. Import the tag template

Templates → Tag Templates → New → ⋮ → Import, choose the file, then Save.

Download pixelfly-cod-protection.tpl

2. Create the hold tag

Tags → New, tag type PixelFly COD Protection. Fill in:

Field in the tagSet it to
Server hostname / base URLYour Hold URL's host, without /cod/hold
API Key (X-PF-Key)Your container's API Key (pf_live_…)
How to read order fieldsAuto from event (Recommended)
Skip request if payment_method is not CODTicked

Auto reads transaction_id, value, currency, email, phone, fbp and fbc off the event. Only switch to Map fields manually if your event uses unusual field names.

3. Create the “hold” trigger

Triggers → New → Custom. Name it something like purchase – COD hold. Add all three conditions:

VariableOperatorValue
Client NameequalsGA4
Event Nameequalspurchase
Event Data cod_holdequalstrue

Set this as the hold tag's firing trigger.

No cod_hold flag coming from your web container yet? Use Event Data payment_method equals cod for now, and add the flag in the web container steps below.

4. Block your purchase tags — the step to be thorough about

Open every tag that sends a purchase and add the trigger from step 3 under Exceptions. Work through the list and tick them off:

  • ☐ Meta / Facebook Conversions API
  • ☐ GA4 server tag
  • ☐ Google Ads conversion
  • ☐ TikTok Events API
  • ☐ Anything else of your own that sends a purchase

A tag you forget here will keep counting COD orders that were never confirmed — and it will look like it is working, which is why the test at the end matters.

5. Let confirmed orders back in

This is how the order returns days later, once you approve it.

  1. Install the Data Client (by Stape), and set its accepted path to /data.

    Easiest: in your server container, Templates → Client Templates → Search Gallery, search Data Client, pick the one by Stape, and Add to workspace. Then Clients → New → Data Client.

    Or import the file if your container cannot reach the gallery: Templates → Client Templates → New → ⋮ → Import.

    Download Data Client (.tpl)

    The Data Client is made and maintained by Stape, not PixelFly. We mirror it so the setup does not stall; prefer the gallery copy when you can, since it updates itself.

  2. Create a second trigger — Triggers → New → Custom, name it purchase – COD confirmed:
    VariableOperatorValue
    Client NameequalsData Client
    Event Nameequalspurchase
    Event Data is_delayedequalstrue
  3. Add this trigger as an extra firing trigger on the same purchase tags you just added exceptions to.

Each of those tags ends up with two firing triggers (your normal one, plus this) and one exception. That is correct: the exception blocks the order at checkout, and this trigger fires it when confirmed.

If you use Transformations: any “Exclude parameters” transformation must list the hold tag among its excluded tags. Otherwise the hold tag loses transaction_id and fails with “missing order_id” — while Preview still shows the ID, because Preview's Event Data is what arrived before transformations ran. That mismatch sends people hunting in the wrong place for hours.

Web container

6. Mark COD purchases, and keep the fields

On the GA4 purchase event you send to sGTM, add two parameters:

ParameterValue
payment_methodThe order's payment method
cod_hold"true" for a COD order while COD is switched on

How to produce cod_hold

It is not a field your store sends — you build it in GTM from three small variables, so one switch controls the whole setup. Create them in this order (Variables → New).

a. Three Data Layer Variables

Type Data Layer Variable, one each, named exactly for the key they read:

Variable nameData Layer Variable Name
dlv - ecommerce.payment_methodecommerce.payment_method
dlv - payment_methodpayment_method
dlv - ecommerce.payment_typeecommerce.payment_type

b. cJS - PixelFly payment method

Type Custom JavaScript. Different carts use different keys, so this takes whichever one is present:

function() {
  return {{dlv - ecommerce.payment_method}} || {{dlv - payment_method}} || {{dlv - ecommerce.payment_type}} || '';
}

c. PixelFly - COD Protection

Type Constant, value off. This is your master switch — set it to on when the rest is built and published. Keeping it off while you work means a half-finished setup never holds a real order.

d. cJS - PixelFly COD hold

Type Custom JavaScript. This is the one the trigger and the cod_hold parameter read:

function() {
  if ({{PixelFly - COD Protection}} !== 'on') return 'false';
  var method = String({{cJS - PixelFly payment method}} || '').toLowerCase();
  if (!method) return 'true';
  var cod = method === 'cod'
    || method.indexOf('cash_on_delivery') !== -1
    || method.indexOf('cash on delivery') !== -1;
  return cod ? 'true' : 'false';
}

It returns the strings 'true' / 'false', not booleans, so trigger conditions compare cleanly. Note an order with no payment method at all is held — an unknown method is more often COD than not.

e. 📌 prevention_reload

Type Custom JavaScript. Our COD trigger also checks this is false, so refreshing the thank-you page does not hold the same order twice:

function(){
  return window.performance.getEntriesByType('navigation')
    .map(function(nav){ return nav.type })
    .includes('reload');
}

Skip this only if your purchase event already fires once per order by some other means.

And make sure these still reach the server — they are what the confirmed order will be sent with, days later:

  • ☐ transaction_id, value, currency
  • ☐ customer user_data (email, phone)
  • ☐ gclid
The gclid must be passed explicitly. Read it from the _gcl_aw cookie and send it as a parameter — your sGTM domain cannot read your site's cookies. Leave it out and Google Ads has nothing to credit the sale to when the confirmed order arrives.

7. Block the browser purchase tags

Same idea as step 4, now in the browser. Make one Custom Event trigger on purchase where cJS - PixelFly COD hold equals true and 📌 prevention_reload equals false, then add it under Exceptions on each:

  • ☐ Meta Pixel
  • ☐ Google Ads conversion
  • ☐ TikTok Pixel
  • ☐ Anything else of your own that sends a purchase

Next

Publish the server container first, then the web one.

Then place one prepaid and one COD test order. On a hand-built setup this test is not optional — a single missed exception in step 4 or 7 is invisible until you watch a COD order go through and see which tags fire.

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