Metrivo
Back to blog

net revenue attribution

Net Revenue Attribution: What Refunds and Chargebacks Do to Your Channel Numbers

Your channel ROI is computed on gross payments. Refunds and chargebacks land weeks later and are never netted back. How to restate attributed revenue to net, with real dispute timelines.

19 min read
Net Revenue Attribution: What Refunds and Chargebacks Do to Your Channel Numbers - Metrivo guide cover illustration

The gross-revenue assumption nobody wrote down

Concise answer

Every attribution tool writes a revenue row when a payment succeeds. Almost none of them ever go back and revise it. That silent assumption is the source of the error.

Consider what a typical attribution pipeline actually does. A visitor arrives with a campaign parameter. A session is recorded. Days or weeks later a payment webhook arrives carrying enough identity to link it back. The tool writes a row: this campaign produced this much revenue on this date. The row is then treated as a fact.

It is a fact about one moment. It says that money moved on that day. It does not say the money stayed. Three weeks later the customer requests a refund, or their bank files a chargeback, and the money moves back. In the payment provider's ledger this is visible and correct. In the attribution dashboard the original row is untouched, because nothing in the pipeline is listening for the event that would revise it, and because the revision event does not look like a sale.

The result is a systematic, one-directional overstatement of every channel's contribution. It is never an understatement, because revenue does not spontaneously appear later. And it is unevenly distributed, which is the part that actually costs money: if every channel over-reported by the same percentage the rankings would survive, but they do not.

This is a different failure from the ones that dominate attribution troubleshooting. Lost UTMs, direct-traffic inflation, and unmatched payments are all failures to connect a sale to a source. This one connects the sale correctly and then fails to disconnect it when the sale is undone. If you are still working on the first class of problem, reduce unattributed revenue covers it; this article assumes your matching already works.

Why the reversal cannot attribute itself

A refund or a dispute arrives with none of the marketing context of a purchase. There is no landing URL, no referrer, no campaign parameter, no click ID, because the customer did not come from an ad to get their money back. They contacted support, or they contacted their bank.

So the reversal has exactly one route to a channel: the identifier of the payment it reverses. If your pipeline retains that link, attribution is a join. If it does not, the reversal is orphaned and can only be reported as an unattributed total, which is precisely as useful as not reporting it.

This is why the fix is architectural rather than analytical. You cannot restate channel revenue after the fact if the link was never stored. The decision has to be made in the webhook handler, before anyone asks the question.

The scale of the distortion

Whether this matters to you is an empirical question with a cheap answer: compute your overall reversal rate as reversed amount over gross amount for a period that has fully closed, then compute it separately per channel. Two minutes of SQL against your payment provider's data.

If the per-channel rates are within a point of each other, note it and move on; gross ranking is close enough for your business. If they diverge materially, you have been making budget decisions on the wrong ordering, and the size of that error is now quantified rather than assumed. Either outcome is worth the two minutes.

Do not assume the answer. Refund propensity varies with buyer intent, and buyer intent varies enormously with how someone found you. Discovery through a comparison listing, a discount-driven campaign, and a technical documentation page do not produce the same buyers.

Refund, dispute, chargeback: three different events

Concise answer

A refund is you returning money voluntarily. A dispute is the cardholder's bank taking it, with a fee, provisionally. They differ in cost, timing, reversibility, and what they tell you about the channel.

These get used interchangeably in conversation and they are not interchangeable in your data. The distinctions change both the accounting and the interpretation.

A refund is initiated by you. You decide the amount, it can be partial, it is immediate, and it is final. The money returns to the customer and the transaction is closed. It costs you the revenue and, depending on your provider and region, possibly some of the processing fee.

A dispute is initiated by the cardholder through their bank. Stripe's documentation describes what happens on initiation: the card network pulls the funds for the dispute from your Stripe balance, these funds are held for the entire duration of the dispute, and Stripe in turn debits your Stripe balance for the disputed amount plus a dispute fee. You cannot issue a refund outside the dispute process while the dispute is open, and your dispute rate with that card network increases.

A chargeback is the outcome of a dispute upheld in the cardholder's favour. The colloquial use of chargeback to mean any dispute is fine in conversation and wrong in a data model, because a disputed payment has a provisional state that a chargeback does not.

Reversal types and their properties, per Stripe's disputes documentation
PropertyRefundDispute (open)Dispute (lost)Dispute (won)
Initiated byYouCardholder via their bankBank decisionBank decision
Funds positionReturned to customerDebited and heldStays with cardholderReturned to you
Extra feeProvider-dependentDispute fee debited immediatelyFee generally non-refundableReceived fee generally not returned
ReversibleNoPending outcomeNoNot applicable
Partial amountsCommonPossible; may differ from the chargePossiblePossible
Signal about the channelProduct or expectation mismatchUnrecognised charge, or fraudSame, confirmedClaim was invalid

The disputed amount may not equal the charge

This trips up any restatement built on the assumption that a reversal offsets its original payment exactly. Stripe's documentation lists several reasons the disputed amount can be lower or higher than the original charge.

Currency conversion is one: Stripe's example describes a purchase for 100 EUR converting to 113.74 USD on the customer's account in January, disputed in April at a changed exchange rate, arriving back at the business as 107.86 EUR rather than the original 100 EUR. Recurring payments are another: Stripe notes that when an account owner disputes multiple payments within a recurring subscription plan, their bank sometimes creates a single dispute for the total amount against one of the charges, giving the example of three 50 USD charges disputed as a single 150 USD dispute against one of them.

There are also partial disputes, where only a portion of the transaction is contested, and disputes on partially refunded charges, where the cardholder disputes the entire payment after you have already refunded part of it. Your restatement logic has to cope with a reversal that exceeds the remaining balance of the original payment without producing negative attributed revenue for the channel.

Inquiries, pre-disputes, and what not to count

Not every dispute-shaped event is a reversal. Stripe describes an inquiry as a preliminary phase before a formal dispute, sometimes called a retrieval or a request for information, used most often by American Express and Discover, with Mastercard and Visa no longer using it. In the API these carry statuses prefixed with warning, and Stripe notes that if an inquiry remains open for 120 days without escalating to a chargeback, it is marked as closed.

Separately, early fraud warnings are messages sourced from issuer fraud reports, including Visa TC40 and Mastercard SAFE reports. Stripe's documentation states that unless the payment was covered by the liability shift rule, 80% of EFWs convert into a fraud dispute if you do nothing, but an EFW is a warning, not a money movement.

The rule for your data model: only count events that actually moved money. An inquiry that never escalates reversed nothing. Counting warnings as reversals will make your net numbers pessimistic in a way that is just as wrong as the gross numbers being optimistic.

The timing problem, and why last month's number is provisional

Concise answer

Reversals arrive in a different period from the sale. Stripe documents a typical 120-day dispute-initiation window and a full lifecycle of 2-3 months, so a month's attributed revenue is not final for roughly six months.

This is the constraint that makes net attribution genuinely difficult rather than merely neglected. If reversals arrived instantly, you would net them and be done. They do not.

Stripe's disputes documentation states that card networks typically allow cardholders to initiate disputes within 120 days of the original payment, but their rules allow more time in some situations, and notes that when a customer pays for a future event or service, the dispute window generally starts on the event date rather than the payment date. After a chargeback is created, Stripe documents that you have usually 7-21 days to respond depending on the card network, and that if you submit evidence the issuer usually has 60-75 days to decide. The documentation summarises the whole thing plainly: the full dispute lifecycle, from initiation to the final decision, can take 2-3 months to complete.

Local payment methods extend it further. Stripe notes that these typically allow customers up to 180 days to file a dispute, compared to the 120-day window common for card payments.

Add the initiation window to the resolution window and a payment made on 1 March can still be reversed, and that reversal finally resolved, well into the following autumn. Any channel-revenue figure for March is therefore an estimate until then.

Documented dispute timing windows and their effect on reporting
StageDocumented windowWhat it means for a monthly channel report
Dispute initiationTypically within 120 days of the payment; longer in some situationsA closed month can still lose revenue four months later
Future-dated servicesWindow starts on the event date, not the payment datePrepaid or scheduled services extend exposure beyond 120 days
Your response deadlineUsually 7-21 days, by networkAn operational deadline, not a reporting one; missing it forfeits the amount
Issuer decisionUsually 60-75 days after evidenceThe provisional debit sits in your numbers for most of a quarter
Full lifecycle2-3 months to completeCombined with initiation, roughly six months to final
Local payment methodsTypically up to 180 days to fileExtend the provisional period accordingly

Restate the sale's period, not the reversal's

There is a real choice here and only one answer serves channel decisions. You can book the reversal in the period it occurred, or you can restate the period of the original sale. Accounting practice often does the former; Stripe's revenue recognition documentation shows a January payment of 90 USD refunded in February appearing as revenue in January and as a refund contra-revenue entry in February.

For attribution, that is the wrong shape. You want to know what a campaign that ran in January actually produced. Booking its refunds against February makes January look better than it was and February look worse, and if spend shifted between the two months the comparison becomes meaningless.

So restate. Attribute the reversal to the period, campaign, and source of the original payment, and re-emit the corrected figure for that period. Which means your channel reports must be re-runnable rather than snapshotted, and every consumer of them must expect historical figures to move.

Publish the maturity of every number

A number that will change should say so. The cheapest honest implementation is a maturity label on every period: provisional while inside the dispute-initiation window, settling while disputes remain open, and final once both have passed.

This costs one column and removes an entire class of argument. Nobody has to relitigate why last month's channel revenue moved, because the report said it would. It also prevents the opposite failure, where a team stops trusting the report entirely because the numbers occasionally change without explanation.

Stripe's revenue recognition documentation offers a related option worth knowing about: it describes a Record refunds separately setting, where by default the refund offsets the revenue so neither amount appears in the report, and enabling it shows both the revenue and the refund with the same net result. Showing both is generally better for diagnosis, because a channel with high gross and high refunds is a different problem from a channel with low gross.

A money-state model that survives reversal

Concise answer

Store the reversal as its own object with an explicit money state and finality, linked to the original payment. Do not decrement a total in place, because a decremented total cannot be audited or corrected.

The tempting implementation is to subtract. A refund arrives, you find the payment row, you reduce its amount. It is one line of code and it destroys your ability to answer any subsequent question.

Once subtracted, you cannot tell a 100 payment refunded by 30 from a 70 payment. You cannot distinguish a refund from a lost dispute. You cannot reverse a won dispute, because you no longer know what was disputed. And you cannot reconcile against your provider, because your totals now differ from theirs by amounts you cannot itemise.

The alternative is to keep every money movement as its own immutable record, carrying the identifier of the payment it relates to, and to derive net figures by projection rather than by mutation. Metrivo's canonical model does this: a refund is recorded with object type refund, a kind of refund or partial_refund, a money state of refunded or partially_refunded, and a finality of reversed. A dispute is recorded with object type dispute, a kind of dispute_opened, dispute_won, or dispute_lost, and a finality of provisional while it is open and final once decided.

That last distinction is what lets an open dispute be shown as at-risk rather than lost. A provisional debit is not the same as a confirmed reversal, and a channel report that treats them identically will overcorrect on every dispute you go on to win.

What the reversal record must carry

The original payment identifier, without which no attribution is possible. The amount and currency of the reversal itself, which may not match the original. The provider's own event identifier, so that duplicate deliveries do not double-count. A timestamp for the reversal and a separate one for the original payment, since restatement needs both.

Also worth recording explicitly: the reason the reversal could not be processed, when that happens. Metrivo's provider mappers carry an exclusion reason on records they cannot use, for example when a refund arrives without the original amount, or when a dispute event is not itself a revenue movement. An excluded record with a stated reason is auditable; a silently dropped one is not.

Provider differences are real

Reversal semantics are not standardised across payment providers. Some emit a distinct refund object; some express reversals as adjustments with an action type; some report a dispute opening and its resolution as separate events and some do not report the resolution at all.

Metrivo normalises Stripe, Razorpay, Paddle, Dodo, Lemon Squeezy, Polar, and the Manual Payment API into one canonical shape so that a net figure spanning providers is computed on comparable definitions. If you are building this yourself, budget for the normalisation rather than assuming a shared vocabulary; the differences show up exactly where they are most expensive, in partial reversals and in dispute outcomes.

The general principle is the same one that applies to every cross-provider metric: normalise first, aggregate second. Aggregating provider-native shapes produces a number whose definition changes with your provider mix. The payment webhook audit checklist covers proving that the events you rely on are actually arriving and being retained.

Running the restatement

Concise answer

Join reversals to their original payments, aggregate by the original payment's source and period, and produce gross, reversed, and net side by side with a maturity label.

Concretely, for a chosen period:

  • Take every payment whose success date falls in the period, with its attributed source and confidence label. This is your gross attributed revenue by channel, and it is what you have today.
  • Take every reversal record of any type, from any date, whose original payment falls in that period. Not reversals that occurred in the period. This is the inversion that makes the restatement work.
  • Exclude reversals that did not move money: open inquiries, warnings, and any provider event your mapper marked as excluded. Count them in a separate at-risk column if they are still live.
  • For disputes still open, report them as at-risk rather than reversed, and show the amount separately. Resolve them into reversed or recovered when the outcome arrives.
  • Compute net as gross minus confirmed reversals, per channel, in the original currency. Do not convert across currencies inside the calculation.
  • Label the period: provisional, settling, or final, based on how far past the payment dates the dispute-initiation window has run.
  • Report gross, reversed, at-risk, and net together. A channel with high gross and high reversal has a different problem from one with low gross, and collapsing to net alone hides which one you have.

Reading a channel's reversal rate

A high reversal rate is a symptom with several possible causes, and they call for opposite responses. Expectation mismatch means the channel's messaging promises something the product does not deliver, and the fix is copy. Fraud concentration means the channel is attracting card testing or stolen-card purchases, and the fix is risk rules at checkout. Billing-descriptor confusion means customers do not recognise the charge on their statement and dispute it in good faith, and the fix is one setting in your payment provider.

The third of those is worth checking first because it is nearly free to fix and disproportionately common in SaaS, where the legal entity name on the statement often bears no resemblance to the product name the customer bought.

You can usually separate these by looking at the split between refunds and disputes. Expectation mismatch tends to produce refund requests, because the customer contacts you. Descriptor confusion and fraud tend to produce disputes, because the customer contacts their bank instead. A channel whose reversals are overwhelmingly disputes rather than refunds is telling you something specific.

Keep the confidence labels attached

Every restated figure inherits the confidence of the underlying attribution. If a payment was matched to a channel by inference rather than by a confirmed identifier, its reversal is attributed to that channel with the same uncertainty, and the net figure should carry it forward rather than laundering it into a clean-looking number.

Metrivo labels matches as confirmed, inferred, or unknown, documented under attribution confidence. A net-of-reversal figure built mostly on inferred matches is a directional indicator, not a settled fact, and presenting it as the latter is how attribution work loses credibility inside a company.

Where reversals cannot be joined to an original payment at all, report them as an unattributed reversal total. Do not distribute them proportionally across known channels; that manufactures precision and, worse, spreads a problem that may be concentrated in one channel evenly across all of them, which is the exact opposite of what you need to know.

What this does not solve

Concise answer

Net attribution corrects the amount. It does not correct the match, it does not make short periods readable, and it does not tell you why a customer reversed.

The honest limits, which belong next to the number whenever it is presented.

  • It inherits your matching quality. If payments are attributed to the wrong channel, netting reversals against them relocates the error rather than removing it.
  • Recent periods stay provisional. There is no method that makes last week's channel revenue final, and any tool that presents it as final is wrong rather than fast.
  • Reversal reasons are coarse. Provider reason codes describe the claim, not the truth, and a fraud-coded dispute is not proof of fraud.
  • Small channels are unreadable. A channel with 20 payments and 2 reversals has a 10 percent rate and no statistical content. State a volume floor and report below it as unknown.
  • Won disputes still cost money. Stripe states that the dispute received fee is generally not returned even when you win and that the dispute countered fee is returned if you win. A won dispute restores revenue but not the full economics, and a pure revenue restatement will slightly flatter channels with high dispute volume.
  • It says nothing about the customers who did not reverse but should have. Silent dissatisfaction shows up later as churn, not as a refund, which is a different measurement entirely.

Frequently asked questions

What is net revenue attribution?

Net revenue attribution reports the revenue each channel produced after refunds and chargebacks are subtracted from the payments that channel generated. It differs from standard attribution, which records revenue when a payment succeeds and never revises it. Producing net figures requires joining each reversal back to its original payment and restating the period of that original payment.

Why are refunds not attributed to a marketing channel automatically?

A refund or chargeback carries no campaign parameters, referrer, or click ID, because the customer did not arrive from a campaign to reverse the payment. The only route to a channel is the identifier of the original payment. If that link is not stored when the reversal event is received, the reversal can never be attributed and can only be reported as an unattributed total.

How long can a customer file a chargeback?

Stripe documents that card networks typically allow cardholders to initiate disputes within 120 days of the original payment, with more time allowed in some situations, and that for services delivered at a future date the window generally starts on the event date rather than the payment date. Local payment methods typically allow up to 180 days. The full dispute lifecycle can then take a further two to three months.

Should a refund be booked in the month it happened or the month of the sale?

For accounting, the reversal is commonly booked in the period it occurred. For channel attribution, restate the period of the original payment instead, so that a campaign's reported return reflects what that campaign actually produced. This makes historical channel figures mutable, which is why each period should carry a maturity label.

Is a dispute the same as a chargeback?

Not exactly. A dispute is the process a cardholder initiates through their bank; a chargeback is the debit that results. Stripe debits the disputed amount plus a dispute fee when the dispute is opened and holds the funds for the duration, and the dispute can still be won, in which case the amount is returned. Treating an open dispute as a confirmed loss will overstate reversals on any channel you successfully defend.

How does Metrivo record refunds and disputes?

Metrivo records each reversal as its own canonical object rather than reducing a payment total in place. Refunds carry an object type of refund with a money state of refunded or partially refunded and a finality of reversed; disputes carry an object type of dispute with kinds for opened, won, and lost, and a finality of provisional while open. Records that cannot be used carry an explicit exclusion reason so gaps are auditable rather than silent.