Chargebacks and reversals: when a network takes the money back

Barely anyone documents this, and it is the part that touches your balance.

A conversion is not final on the day it happens. An advertiser can determine, later, that it was fraudulent, duplicated, or cancelled — a trial refunded, a deposit charged back, an install from an emulator farm — and take the money back.

Most integration guides stop before this part. It is the part that touches your balance.

How a reversal arrives

A reversal does not arrive as a new event with a new identifier. It re-sends the original conversion with the same transaction id and a status meaning "taken back", sometimes with a negative amount.

That single detail causes more bugs than anything else in this area, because the obvious first thing any postback endpoint does is check for duplicates — and a reversal looks exactly like a duplicate. An endpoint that dedupes before it looks at the status answers "already have it" to every reversal it ever receives, and nothing is ever clawed back.

We shipped that bug and found it in production. The fix is ordering: a chargeback is exempt from the duplicate check and made safe a different way — by only ever acting on a conversion that is still credited, so a reversal delivered twice reverses once.

If you are writing an endpoint, put the status check first.

What you receive

A reversal postback carries the same transaction id as the original, a negative amount, and a status of reversed. Both are sent deliberately: some systems key off the sign, some off the status, and you should not have to rewrite your endpoint to match ours.

Handle it with whichever your system already reads, and make the debit conditional on the credit still standing.

What it does to your balance

The amount comes off, on a ledger entry of its own, referencing the original conversion. The books stay readable: you can see what was credited, what was reversed, and when.

There is no setting to opt out of this, and that is deliberate. There used to be a choice between deducting a reversal and absorbing it, and it was a promise nobody can keep — the money is taken back from us regardless of what a checkbox on our side says. All that choice decided was who pretended to eat the loss.

The user who already spent it

This is your decision, and the reason we pass the reversal on rather than quietly absorbing it.

Three defensible policies:

  • Deduct it. Take the currency back if the balance covers it. Correct, and it will produce a support ticket.
  • Absorb it. Let the user keep it and take the loss yourself. Clean, and it is exactly what a fraud ring is counting on.
  • Deduct, and allow a negative balance. The user cannot withdraw again until they are square. Harsh on someone who did nothing wrong, effective against someone who did.

Most publishers land on a mix: absorb small amounts silently, deduct large ones, and look at any account with a pattern. Whatever you choose, write it in your terms before you need it. Deciding your reversal policy in the middle of an argument with a user is how you end up with a policy you did not want.

What a normal rate looks like

A few per cent is ordinary. Zero means nobody is checking, not that your traffic is perfect.

What is worth investigating is a change: a spike concentrated in one offer, one country, or one user. That is usually a single leak rather than a general decline, and it is findable — every conversion is stored with its offer, its country and its user, including the ones that were refused.

Why we store refused conversions too

Because support has to be able to say "the conversion did arrive, and here is why it was not paid". A conversion that was screened out and deleted is a conversation that ends in a shrug. One that was screened out and recorded is a conversation that ends with an answer.

Keep reading