40% off Cloud Workspace for 24 months

Claim 40% off
FLYNODE
Server GTM Containers

COD Order Protection

Hold cash-on-delivery purchases at checkout and send them to Meta, GA4, Google Ads and TikTok only when the order is really confirmed.

8 min readUpdated Nov 5, 2026FlyNode docs
Browse documentation

Need a hand?

Can't find your answer? Our team replies fast.

Contact support

With cash on delivery, a purchase at checkout is not a sale yet. Some orders are fake, some are cancelled on the confirmation call, and some come back with the courier. If your purchase tags fire at checkout, the ad platforms learn from all of them, and they go looking for more people who order and never pay.

COD Order Protection holds a cash-on-delivery purchase in FlyNode when it happens. When you confirm the order, FlyNode sends it back into your server container as a purchase with the original order time and the original ad click, and your own tags send it on. Orders you reject are never sent. It works on every plan.

How it works

  1. Your web container sends the GA4 purchase as usual, with one extra parameter, cod_hold, that is true for cash-on-delivery orders.
  2. In your server container, the FlyNode COD Hold tag sends that purchase to FlyNode. Your other purchase tags skip it (an exception), so nothing reaches the ad platforms yet.
  3. The order waits on the COD Order Protection page of your container. You confirm or reject it there, or your shop does it for you (WooCommerce or a webhook, see below).
  4. A confirmed order goes back into your server container through the FlyNode COD Client, as a purchase event with is_delayed set to true. Your Meta, GA4, Google Ads and TikTok tags fire on it with the order's own ID, value, items, customer details and click IDs.

If FlyNode cannot hold an order (for example while FlyNode is down, or after you switch the power-up off), the hold tag sends the purchase straight back into your container through the same client, so your tags fire at once as they did before. You do not lose a sale because of FlyNode.

Set it up

You need a server container on FlyNode with a GA4 client, and a web container that sends the GA4 purchase event to it.

1. Switch it on

Open your container, go to Power-Ups and turn on COD Order Protection. Your container restarts once.

Do this before step 2. Until the power-up has been on once, the hold tag does not know where to send an order, so it cannot hold it or pass it on. With the exceptions from step 2 already on your purchase tags, a COD purchase would then go nowhere.

2. Server container

The quickest way is the ready-made container from the power-up: in your server container open Admin, Import Container, choose flynode-cod-server-container.json, pick your workspace and Merge (rename conflicting items). It adds:

  • the two templates, FlyNode COD Hold (tag) and FlyNode COD Client (client),
  • a FlyNode COD Client client, which answers on /flynode-cod,
  • a FlyNode COD Hold tag with the trigger FlyNode - COD purchase to hold (Client Name is GA4, Event Name is purchase, cod_hold is true),
  • a trigger FlyNode - COD confirmed purchase (Client Name is FlyNode COD Client, Event Name is purchase).

Then, on every other purchase tag in the server container (Meta Conversions API, GA4, Google Ads, TikTok and so on):

  1. add FlyNode - COD purchase to hold as an exception, so the tag skips a COD order at checkout;
  2. add FlyNode - COD confirmed purchase as a second firing trigger, so the tag sends the order when it is confirmed.

If you would rather build it by hand: download both templates from the power-up, import them under Templates (the three-dot menu, Import), and create the client, tag and triggers as listed above.

3. Web container

Import flynode-cod-web-container.json the same way (Merge). It adds a variable FlyNode - payment type (the data layer value ecommerce.payment_type), a variable FlyNode - cod_hold (true for cod, cash_on_delivery or cash on delivery) and a trigger FlyNode - COD purchase. Then:

  1. In your GA4 purchase event tag, add two event parameters: cod_hold with the value {{FlyNode - cod_hold}}, and payment_type with {{FlyNode - payment type}}.
  2. On every browser purchase tag that goes straight to an ad platform (Meta Pixel, Google Ads, TikTok Pixel), add FlyNode - COD purchase as an exception. Otherwise the browser still reports the order at checkout.

If your data layer keeps the payment method under another name, change FlyNode - payment type to read it. If your shop sends something other than cod for cash on delivery, add it to the list in FlyNode - cod_hold and in the hold tag's Payment methods field.

4. Publish both containers

Then test, below, before you rely on it.

Confirm or reject orders

On the COD Order Protection page

Pending orders show the order ID, value, customer name, phone and city, and whether the order came with a Google click or a Meta browser ID. Approve & fire sends the order now; Reject drops it for good. You can tick several and do them at once. If other orders came from the same phone number, the row says so, which helps spot fake orders.

From WooCommerce (no plugin)

  1. On the power-up, under Confirm orders automatically, copy the Delivery URL and the Webhook secret.
  2. In WordPress open WooCommerce, Settings, Advanced, Webhooks, Add webhook. Status Active, Topic Order updated, API version WP REST API Integration v3, and paste the Delivery URL and the secret (into Secret). Save.

By default an order is sent on when its status becomes Completed, because WooCommerce marks every cash-on-delivery order Processing as soon as it is placed. If your shop only moves an order to Processing after the confirmation call, choose Processing on the power-up. Cancelled, Refunded and Failed reject the order. FlyNode matches the order by its number or its ID, so it works whichever one your data layer sends as the transaction ID.

From any other shop, n8n or a CRM

POST JSON to the Webhook URL with the secret in the X-Flynode-Webhook-Secret header:

curl -X POST https://api.flynode.io/flynode-api/cod/YOUR_WEBHOOK_ID \
  -H 'Content-Type: application/json' \
  -H 'X-Flynode-Webhook-Secret: fnwh_...' \
  -d '{"order_id":"1234","status":"completed"}'

These statuses send the order: confirmed, processing, completed, paid, shipped, delivered, approved. These reject it: cancelled, failed, refunded, returned, rejected, fake. Anything else is ignored. Send up to 100 orders at once as {"orders":[{"order_id":"1","status":"delivered"}, ...]}. The answer says what happened to each order.

The webhook secret starts with fnwh_. It is not the key FlyNode uses inside your container; if you send that by mistake, the answer tells you. If a status arrives before the order was held (a fast shop, a slow page), FlyNode keeps it for 7 days and applies it when the order comes in.

Test it

  1. Turn on the Preview Header power-up with your Tag Manager preview header, so the requests FlyNode sends into your container show up in Preview.
  2. Place one prepaid test order. It must go out at once, as before.
  3. Place one cash-on-delivery test order. In Preview, the GA4 purchase fires only the FlyNode COD Hold tag, and the order appears as Pending on the power-up.
  4. Press Approve & fire. In Preview you see a new purchase from the FlyNode COD Client with is_delayed true, and your purchase tags fire on it.
  5. Check the browser side too: the Meta Pixel and Google Ads purchase tags must not fire for the COD test order.
  6. Turn the Preview Header off again when you are done.

Timing: send confirmations soon

  • Meta accepts the original order time for 7 days. An order confirmed later is sent with the time it is sent.
  • GA4 keeps the original time only for about 72 hours.
  • Google Ads needs a Google click ID (gclid, wbraid or gbraid) on the order, and the click must be within about 90 days.

The page warns about pending orders that are close to these limits. A pending order that nobody confirms or rejects becomes Expired after 30 days; you can still approve it later.

How many orders

Holding orders is free and not limited. What counts is each order sent on (a fire): 50 a month on Free, 5,000 on Pro, 10,000 on Business and Enterprise. The power-up shows how many you have used this month, and FlyNode emails you at 80% and at 100%. Over the limit, confirmed orders wait and go out when the next month starts or when you upgrade.

Good to know

  • FlyNode cannot see inside your tags. The page shows whether your container took the order and, when Tag Manager reports it, which tags ran. Use Preview to check a tag's own result.
  • If your container does not answer, FlyNode tries again for 24 hours, then marks the order Send failed with a Retry button. The usual cause is that the FlyNode COD Client is not published.
  • Held orders, including the customer details, are kept encrypted by FlyNode and shown only to you and your team. They are never shown to other FlyNode customers.
  • Only requests signed by FlyNode are accepted on /flynode-cod, so nobody else can push purchases into your container through it.
  • Turning the power-up on or off restarts the container once. When it is off, cash-on-delivery purchases go out at checkout again (the fallback); you can leave the tags in place.

Still stuck?

Open a support ticket from your dashboard and include the container name.

Contact support