Ad platforms and webshops

Cash on delivery conversion tracking and external checkouts

Cash on delivery conversion tracking: why pixels miss COD and external checkout orders, when to send the purchase, and how to handle refused orders.

·Updated ·11 min read·By the ROASaurus team

Key takeaways
  • Browser pixels miss orders that finish outside the normal checkout, such as cash on delivery forms, external checkouts, order forms and funnels.
  • A cash on delivery order stays unpaid until the courier collects the cash, so a setup that sends purchases on payment reports it late or never.
  • Meta rejects events with an event time more than 7 days in the past, so most cash on delivery stores should send the purchase when the order is created or confirmed.
  • Refused orders stay in Ads Manager, so divide your break-even ROAS by the share of orders that get paid.
  • In ROASaurus, choose External / COD checkout in Store settings to send Shopify purchases when the order is created.

Cash on delivery conversion tracking means sending each purchase to your ad platforms from the order itself, on the server, instead of relying on a browser pixel on a thank-you page. Orders from cash on delivery apps, external checkouts, order forms and funnels often finish outside the normal checkout, so the pixel misses them or reports them at the wrong moment.

This guide explains why those orders go missing and why the payment status matters. It compares three moments to send the purchase: when the order is created, when it is confirmed and when it is paid. It also covers refused orders and refunds, and how ROASaurus handles external checkouts.

Why do pixels miss cash on delivery orders?

A browser pixel reports a purchase when the buyer reaches a page that runs it. That is usually the thank-you page of your checkout. Every order path that skips this page, or runs it on another domain, loses the purchase event or the ad click that belongs to it.

Order pathWhere the order is completedWhat goes wrong with a browser pixel
Cash on delivery form appA form on the product page; the app creates the orderThe store checkout never runs, so its purchase event never fires
External checkoutA checkout run by another app, often on another domainYour pixel may not load there, and the ad click cookies stay on your store domain
Order form or manual orderA form, a chat or a phone call; staff enter the orderThere is no browser session for a pixel to see
Funnel with its own checkoutA landing page builder on its own domainThe ad click is on one domain and the purchase on another
Each path creates a real order, but none of them runs the normal thank-you page with the ad click attached.

The order itself is never lost. It ends up in your webshop or your funnel tool with an order id, an amount, an email address and often a phone number. That is why the reliable fix is to send the purchase from the order data, server-side, and to carry the ad click along to the order. Server-side tracking on Shopify explains the general method. This post covers the orders that need extra care.

Some cash on delivery apps also fire their own pixel events. If they use the same pixel as your server-side tracking but a different event id, the platform counts the order twice. Turn those app events off, or follow the steps to prevent double counting.

Why the payment status matters

A card payment is captured in the checkout, so the order is paid the moment it exists. A cash on delivery order is different. Shopify lists cash on delivery as a manual payment method, and these orders stay Payment pending until you mark them as paid. In practice that happens after the courier collects the cash, which can take days. Some orders are never paid at all, because the buyer refuses the parcel or is not at home.

Many tracking setups send the purchase when the order is paid, because for card orders that is the safest moment. For a cash on delivery order, the same rule means the purchase arrives days late or never. The log below shows a typical case on a store that sends purchases on payment.

14 Oct 10:02 orders/create #4821 $50.00Order placed with cash on delivery, payment pending
14 Oct 10:02 PurchaseNot sent: this store sends purchases when paid
16 Oct 09:40 #4821 shippedParcel handed to the courier
22 Oct 15:12 orders/paid #4821Marked as paid after the courier paid out
22 Oct 15:12 Purchase event_time=14 OctRejected: the event time is more than 7 days old
Example: a purchase that waits for payment. Eight days pass between the order and the paid status, and Meta does not accept an event time more than 7 days in the past.

WooCommerce works the other way around. It sets cash on delivery orders to Processing until the payment is collected, except orders with a downloadable product, which go to On hold. A setup that sends orders in Processing therefore sends them as soon as they are placed. Check which status your platform gives these orders before you decide anything else.

Created, confirmed or paid: when should you send the purchase?

There are three moments to choose from. Each one trades speed against accuracy.

Send the purchase when the order isTypical delay after the orderIncludes orders that are never paidMain risk
CreatedSecondsYes: refusals and fake ordersAds Manager shows more revenue than you collect
Confirmed by your team, by phone or messageHours to a dayRefusals onlyNeeds a confirmation step that can trigger the event
Paid or deliveredDays to weeksNoLate or lost: the ad click falls outside the attribution window, or the event is rejected
Sending on creation is fast but counts orders that never pay. Sending on payment is exact but late.

Why late purchase events are a problem

Ad platforms credit a purchase to an ad only when it happens inside the attribution window after the click. With a 7-day click attribution setting on Meta, a purchase counts for an ad up to 7 days after the click. Meta's Conversions API also refuses events whose event_time is more than 7 days in the past, and it rejects the whole request when one event is too old. A purchase that is sent on payment therefore has two bad options:

  • Send it with the time of the order. If the payment came more than 7 days later, Meta rejects it.
  • Send it with the time of the payment. Meta accepts it, but the ad click may now be more than 7 days old, so the purchase is not credited to the ad.

Speed also matters for optimization. Campaigns that optimize for purchases learn from the purchases you send. A signal that arrives a week late tells the algorithm about last week's audience.

Example store: share of cash on delivery orders marked as paid
100%0%
02468101214
Days after the order, illustrative numbers. About half of the orders are paid by day 4. After 7 days, 28% are still unpaid, including the 18% that are never paid.

Which moment to choose

For most cash on delivery stores, sending on creation is the better default. The signal is immediate, the click is still inside the window, and nothing is rejected. The cost is that refused orders count as purchases in Ads Manager. You correct for that in your targets, as the next section shows.

If your team confirms each order by phone or message before shipping, the confirmation is a good middle ground. It removes fake orders, and it usually happens within a day. Use it when your own system can send the purchase at that moment, for example through an API. Fake orders matter more than they seem: if the ad platform learns from them, it looks for more people who place orders and do not pay.

How to handle refused orders, cancellations and refunds

Once a purchase is sent, it stays in Ads Manager. A parcel refused at the door does not remove it. ROASaurus does not send refunds or cancellations to Meta, TikTok and most other ad platforms either, so the revenue they report includes orders you never collected. Only Google Ads and Microsoft Advertising values can be corrected after a refund, when value corrections are on. The fix is to measure your refusal rate and adjust the numbers you compare against.

Here is a worked example for one month. To keep it simple, prices are without VAT.

LineAmount
Orders sent to Meta on creation200 orders × $50 = $10,000
Refused at the door (18%)36 orders, $1,800
Delivered and paid164 orders, $8,200
Product cost of delivered orders (30%)−$2,460
Shipping for all 200 parcels at $6−$1,200
Return shipping for 36 refused parcels at $6−$216
Cash collection fee, $1.50 per paid order−$246
Handling, $1 per order−$200
Gross profit$3,878
Refused parcels bring in no revenue but still cost shipping both ways. The refused products go back into stock, so their product cost is not lost.

With $3,000 of ad spend, the same month looks very different depending on which revenue you use:

3.33ROAS in Ads Manager: $10,000 ÷ $3,000
2.73ROAS on delivered orders: $8,200 ÷ $3,000
1.29POAS: $3,878 gross profit ÷ $3,000

The gross margin rate on delivered revenue is $3,878 ÷ $8,200 = 47.3%, so the break-even ROAS is 1 ÷ 0.473 = 2.11. That break-even applies to delivered revenue. Ads Manager also counts the refused orders, so you have to translate it before you compare. Break-even ROAS explains the base formula.

Break-even in Ads Manager=break-even ROAS ÷ payment rate
Break-even ROAS
1 ÷ gross margin rate, calculated on paid orders only.
Payment rate
The share of orders that are paid, for example 82% when 18% are refused.

2.114 ÷ 0.82 = 2.58. A campaign that shows less than 2.58 in Ads Manager loses money, even though it looks well above 2.11.

You can check the result directly. The ads break even when the ad spend equals the gross profit of $3,878, and $10,000 of reported revenue ÷ $3,878 = 2.58.

Refusal rates often differ by region, product and campaign. Calculate them per campaign when you have enough orders, and use the POAS calculator to check the result on gross profit.

Refunds and cancellations in your own numbers

On Shopify, cancellations and refunds reach ROASaurus by webhook, so profit analytics counts refunds against revenue. You choose whether cancelled orders that were never fulfilled count, and by default they are left out. A cancelled order that was already shipped stays in, because its shipping still cost you money. For recent orders that may still come back, the Predicted returns setting holds back an expected share of sales until real refunds arrive.

How ROASaurus handles cash on delivery and external checkouts

Shopify

Each Shopify store has a Checkout type in Store settings, with two options:

  • Shopify checkout: the purchase is sent when Shopify reports the order as paid, through the orders/paid webhook.
  • External / COD checkout: the purchase is sent when the order is created, through the orders/create webhook. Use it for cash on delivery, and for external checkouts, such as CheckoutChamp or Checkify, that create the Shopify order themselves.

The setting applies to every order in the store. Card orders that are paid in the checkout are created and paid at almost the same moment, so the choice mainly changes when unpaid orders are sent. Each order is sent once. When the paid update for a cash on delivery order arrives later, no second purchase is sent.

The purchase uses the order's own data: the amount, the currency, the products, and the buyer's email, phone and address, hashed before they leave. ROASaurus matches the order to the ad click through the visitor id that its script saves on the cart. When an order does not carry it, ROASaurus tries, in this order: the cart and checkout tokens, the purchase event from the checkout, an email the visitor gave earlier on your site, and the same IP address and browser shortly before the order, when exactly one visitor fits. The Shopify integration page has the full setup.

WooCommerce and other webshops

The WooCommerce plugin sends an order once it reaches Processing or Completed. Cash on delivery orders reach Processing when they are placed, so they are sent right away. Bank transfer orders are sent when they move to Processing. The other webshop connectors send an order at these moments:

  • Wix, Squarespace, Ecwid, Lightspeed, CCV Shop and Shopware: when the payment status of the order is paid.
  • BigCommerce: when the order reaches a status such as Awaiting Fulfillment, Awaiting Shipment, Shipped or Completed.
  • Magento: when the order state is Processing or Complete.
  • PrestaShop: when the order is marked as valid.
  • Custom webhook and orders API: when your own system sends the order.

Check how your platform marks cash on delivery orders. If they stay unpaid until the courier pays out, their purchases arrive late.

Funnels and your own checkout

  1. Add the script to the funnel pages

    Use the same store key as your webshop, so a visitor keeps one visitor id on both domains.

  2. List your domains

    In Store settings, add the funnel and store domains under Linked domains. Links between those domains then carry the visitor id and the click ids.

  3. Send the orders through the orders API

    If the funnel takes the order itself, post each order to /api/v1/orders with the _tl cookie as visitor_id. Your system decides the moment: on creation, on confirmation or on payment.

  4. Check the Event log

    When the first real orders arrive, open the Event log and check that each one was sent to every connected platform.

An order from a funnel checkout, sent to the orders API
POST /api/v1/orders
X-Roasaurus-Key: <store key>
Authorization: Bearer <API key>
Content-Type: application/json

{
  "order_id": "F-20931",
  "created_at": "2026-10-20T14:05:00Z",
  "total": 50.00,
  "currency": "USD",
  "email": "buyer@example.com",
  "phone": "+15555550123",
  "country": "US",
  "visitor_id": "<value of the _tl cookie>",
  "click_ids": { "fbc": "<_fbc cookie>", "fbp": "<_fbp cookie>" },
  "client_ip": "203.0.113.7",
  "user_agent": "Mozilla/5.0 ...",
  "items": [{ "variant_id": "SKU1", "quantity": 1, "price": 50.00 }]
}

The orders API keeps the first copy of each order id. If your system calls it on creation and again on payment, only the first call sends a purchase. More detail is in funnels and multiple domains.

Common mistakes to avoid

  • Waiting for the paid status on a cash on delivery store. Purchases arrive a week late or not at all, and late events are rejected or fall outside the attribution window.
  • Comparing Ads Manager ROAS with a break-even made for paid orders. Divide the break-even by your payment rate first.
  • Two sources for one purchase. A form app's own pixel and your server events count the same order twice. See duplicate purchase events.
  • Losing the click between domains. A funnel on another domain needs the script and linked domains, or its orders arrive without an ad click.
  • Leaving manual orders out. Phone and chat orders have no browser session, but they still have an email address or a phone number. Sent from the order data, they give the ad platform something to match.

Sources

  1. Meta for Developers: Server Event Parameters
  2. Shopify: WebhookSubscriptionTopic (GraphQL Admin API)
  3. Shopify Help Center: Manual payment methods
  4. Shopify Help Center: Understanding your order statuses
  5. WooCommerce: Cash on Delivery documentation

In ROASaurus

Keep reading

Ad platforms and webshopsCCV Shop conversion tracking: a setup guideCCV Shop conversion tracking: send paid orders to Meta and TikTok with a signed webhook, link them to ad clicks, set up, test and handle late payments.Ad platforms and webshopsGoogle Ads enhanced conversions on ShopifyEnhanced conversions add hashed customer data to Google Ads purchases on Shopify. How they work, how they differ from offline imports, and the setup.Ad platforms and webshopsLightspeed conversion tracking for eCom webshopsLightspeed conversion tracking for eCom (C-Series): send paid orders to Meta and TikTok from the server, link them to ad clicks, set it up and test it.
FAQ

Questions, answered

Can Meta track cash on delivery orders?

Yes, if the purchase is sent from the order data on the server. A browser pixel often misses these orders, because many cash on delivery apps and external checkouts skip the normal thank-you page.

Should I send the purchase when the order is created or when it is paid?

For most cash on delivery stores, send it when the order is created or confirmed. Payment can take more than a week, and Meta rejects events with an event time more than 7 days in the past. Correct for refused orders in your ROAS targets instead.

Do refused cash on delivery orders count as purchases in Ads Manager?

Yes. A purchase that was sent stays in Ads Manager when the parcel is refused. Divide your break-even ROAS by the share of orders that get paid to get the break-even in Ads Manager terms.

Which checkout type should I choose in ROASaurus?

On Shopify, choose External / COD checkout for cash on delivery and for external checkouts, such as CheckoutChamp or Checkify, that create the Shopify order. Purchases are then sent when the order is created instead of when it is paid.

How do I track orders from a funnel with its own checkout?

Add the ROASaurus script to the funnel pages, list the funnel and store domains under Linked domains, and send the funnel orders to the orders API with the visitor id.

Every sale, traced to the ad that earned it

Server-side tracking, profit and lifetime value in one app. 14-day free trial, no credit card.

Start your free trial →