How to calculate true ROAS after returns and COD failures

To calculate a truer ROAS, adjust the revenue side: take platform-reported revenue for the period, subtract cancelled, COD-failed, and returned order value from your store data, then divide the retained figure by ad spend. The result is an estimated retained ROAS, honestly labelled an estimate.

Why the adjustment goes on the revenue side, not the spend side

Your ad spend isn't the part that's wrong. That money left your account, real transaction, nothing ambiguous about it. What's wrong is the other side of the ratio, the revenue a platform counted the second someone finished checkout, before any of that order's actual fate played out. So the fix goes on the revenue side: same spend number you already have, a corrected revenue number sitting on top of it. This is the same Reality Gap covered elsewhere on this blog, here turned into an actual method instead of just a concept.

The five-step method: fix the period, pull reported revenue, map store outcomes to campaigns, subtract to get retained revenue, divide by spend and record both figures.

Five steps, same order every time.

The five steps

  • Fix the period, let the returns window settle — Pick a period, a calendar month is easiest. Don't run this the day the period ends, returns and COD outcomes are still arriving. Wait for the return window to close first, same rule the Retained MER method uses at the account level.
  • Pull reported revenue per campaign — Export campaign-level revenue and ROAS from Meta or Google for that period. This is the number you're about to correct.
  • Pull store outcomes and map them to campaigns where identifiers allow — From Shopify or WooCommerce plus your logistics data, pull cancellations, COD failures, and returns for the same period. Map each one back to the campaign that drove it, wherever a click ID or UTM parameter lets you.
  • Subtract to get retained revenue — Reported revenue minus the value of cancelled, COD-failed, and returned orders equals retained revenue, per campaign.
  • Divide by spend and record both figures side by side — Retained revenue divided by that campaign's spend gives you an estimated retained ROAS. Keep the reported number next to it instead of throwing it away, it's still useful for comparing campaigns against each other on the platform's own terms.

Template showing the method's output: reported ROAS and estimated retained ROAS recorded side by side, with placeholder values since this article uses no specific figures.

What you end up with. Fill in your own numbers, this page doesn't have one to give you.

The mapping problem, honestly

Some orders won't map to a campaign cleanly. No click ID, no UTM parameter, sometimes someone clicked one ad and converted through a completely different session later. Forcing those into a campaign anyway, just so every rupee is accounted for somewhere, means inventing an attribution you don't actually have.

The honest move is stating the unmatched remainder, the orders you genuinely can't map, as its own number instead of spreading them across campaigns by guesswork. It's the same discipline the month-end close uses at the account level, applied here one campaign at a time. A large unmatched remainder isn't this method failing. It's a finding about your own tracking setup, and it's worth fixing before the number underneath it gets trusted for anything bigger than a spreadsheet.

Orders split three ways: matched and reported, matched and retained, and an unmatched remainder that's stated honestly rather than forced into either other column.

A large remainder isn't the method failing. It's a finding about your tracking setup.

The word "true," qualified

Call this what it is: an estimate. Stitching platform exports together with store data by hand, or through a tool automating the same join, gets you a retained ROAS that's a lot closer to real than the platform's own number. It's still built on an estimated join though, not a guaranteed one.

What upgrades an estimate into something harder is deterministic order-level matching, every platform-recorded conversion tied to its specific store order, not categories of orders subtracted from a total. Until that level of matching exists on your account, keep calling the output an estimate. That's not a hedge, it's just the accurate word for what this method actually produces.

Estimated retained ROAS, produced by this method today, sits one upgrade away from deterministic retained ROAS, which requires order-level matching this method doesn't do.

An estimate today. Deterministic when the matching underneath it is.

Doing it continuously

Doing this once, by hand, for one period is a spreadsheet exercise, an afternoon at most. Doing it every period, for every campaign, is what actually changes how budget gets planned, and that's the part manual spreadsheets tend to give up on after a couple of months. Tools in this category, Adverti's true ROAS calculator included, exist to run the same join continuously instead of rebuilding it from scratch each time. Whatever runs it, the output stays an estimate until the matching underneath it is deterministic. That doesn't change just because it's automated.

Frequently asked questions

Same philosophy, different altitude: this method works campaign-level where identifiers allow, [Retained MER](/blog/retained-mer) is the blended account-level ratio. Use both; they answer different questions.

See your own estimated gap. Adverti Chat joins your ad spend with your store and delivery outcomes and shows a stitched estimate of what you actually kept, next to what was reported. Free plan, read-only access, no card.

Share this post


Harsh Bhatt

Harsh Bhatt

August 28, 20266 min read

Start Talking to Your Ads Today.

Connect your Meta and Google accounts. Your first audit is ready in 30 seconds. Free forever on the Free plan.