Billing integrity

The Most Valuable AI Output Was a Refusal to Compute

How a symmetric billing bug made a losing campaign look profitable — and why a production AI system should sometimes refuse to produce an answer.

In a pay-per-call marketplace, a conversion is not something the platform observes. It is a term in a contract. A buyer agrees to pay for a call that satisfies a stated condition — usually a minimum duration of connected talk time — and everything downstream is derived from that one definition: what the buyer is invoiced, what the publisher is paid, what a campaign’s margin is, and what the bidding logic believes a call is worth.

Get that definition wrong in one place and you get a bad number. Get it wrong in a place that feeds both sides of the ledger and you get something worse: a set of numbers that agree with each other, reconcile cleanly, pass every internal check, and describe a campaign that is losing money as one that is doing well.

That is what happened. This is the incident, the postmortem, the controls it produced, and the reason a later version of the same system was right to refuse to answer a question.

The buyer term

The campaign’s buyer paid on a 140-second term. A call had to reach 140 seconds of connected talk time before it counted as a conversion and became billable.

Duration terms exist because duration is the cheapest available proxy for intent. A caller who stays on the line for well over two minutes has, with reasonable likelihood, described a real situation to a real agent. A caller who hangs up at twelve seconds has not. Buyers set the threshold where their own data says the conversation becomes worth paying for, and the number varies widely — thirty seconds in one vertical, well over two minutes in another. There is no platform-wide correct value. There is only the value in each contract.

This makes the settlement term the single most load-bearing configuration value in the system. It is the boundary between a call that generates revenue and a call that generates cost. Every other financial figure is bookkeeping on top of it.

The static override

A legacy configuration path parsed the 140-second buyer term correctly. The value was present, readable, and right where anyone looking for it would expect to find it.

The billing path evaluated conversions against a static 10-second setting inherited from an older conversion model.

So the system read one number and settled on another. This was not a rounding error, a race, or a rare edge case. It was a second source of truth for the same question that had never been removed when the first one was introduced — a dead configuration path that was not, in fact, dead. Every call that reached ten seconds was booked as a conversion on a campaign whose buyer had agreed to pay at 140.

Ten seconds is roughly “the call connected and someone said hello.” It is a threshold that almost every answered call clears. The effect was not that some calls were misclassified. It was that the qualifying condition had been quietly removed.

Why the numbers looked internally consistent

The reason this ran rather than being caught in the first hour is symmetry.

The same conversion event drove both sides of the ledger. When a call crossed ten seconds, the platform booked buyer revenue against it and accrued publisher payout against it. Both entries came from one upstream fact, and that fact was wrong in one direction only. So:

  • conversion counts agreed between the telephony records, the billing rows, and the payout rows;
  • revenue per conversion sat exactly where the buyer’s rate card said it should;
  • payout per conversion sat exactly where the publisher’s terms said it should;
  • margin per conversion was exactly where it should be, because the rates on both sides were applied correctly — what was wrong was which calls counted as conversions at all, and that error scaled revenue and cost together;
  • volume was up, and an elevated conversion rate on a well-matched campaign is not, by itself, an anomaly. It is what success looks like.

The bug did not produce an outlier. It produced a scaled version of a healthy campaign. Every consistency check the platform could run against itself was satisfied — necessarily satisfied, because those checks compare quantities that all descend from the same input.

The disagreement was only visible from outside. In the buyer’s own records, a twelve-second call was not a billable event, and had never been one.

The actual economic damage

MeasureValue
Conversions booked1,185
Conversions that met the actual 140-second buyer term~242
Phantom conversions~943
Booked revenue the buyer did not owe~$18,860
Publisher payout on calls the buyer would not pay for~$15,088
Apparent margin on those calls (derived: $18,860 − $15,088)~$3,772

Roughly four of every five booked conversions did not qualify. The two dollar figures are the part that matters, and the reason they matter is that they are not symmetric in the way the accounting was.

Booked revenue is a claim. A line item the buyer did not agree to pay is an invoice line that gets disputed, credited, and removed — and it should be, because the platform’s own contract says the money was never owed. That figure is reversible, at the cost of a correction and a conversation with a customer about why the last statement was wrong.

Publisher payout is not reversible in the same way. It is an obligation attached to calls the publisher delivered in good faith under terms the platform published. Recovering it is a negotiation with a partner, not a database update, and in a marketplace where supply is the scarce side, it is often not worth the relationship even where it is contractually available.

So the position the reports described — roughly $3,772 of margin on that population of calls — was not merely overstated. It had the wrong sign. Strip out the revenue that was never collectible and what remains is cost. The apparent positive economics concealed an actual loss.

Why normal profit reporting failed as a signal

Profit reporting is the control most people assume catches billing errors. It did not, and it could not, and understanding why is the most transferable part of this incident.

Margin is a ratio. When revenue and cost are both derived from the same event definition, corrupting that definition scales the numerator and the denominator together. The ratio is approximately invariant to exactly the class of error you wanted it to detect. A margin report is a very good instrument for a change in price, and a nearly useless one for a change in what counts as a sale.

The same applies to almost every internal check: totals that foot, counts that agree across tables, per-unit values that fall inside historical ranges. All of them ask whether the system is consistent with itself, which it was.

What would have caught it, in rough order of how quickly:

  • Term-versus-term comparison. The threshold recorded on the campaign’s contract, compared against the threshold actually used at settlement. Two values that must be equal, in a system where a live code path let them differ. This is a check that takes one query and would have fired within a day.
  • Distributional plausibility. A 140-second term produces a billable-duration distribution that is truncated at 140 seconds. This one had mass sitting in the teens and twenties. The shape of the data disagreed with the rule that supposedly produced it.
  • External reconciliation. The platform’s conversion count against the buyer’s accepted count. This is the check that leaves the system, and it is the only category that is immune to symmetric error by construction.
  • Cash against bookings. What was actually collected, compared to what was recognized. The slowest signal, and the one that always eventually tells the truth.

The generalizable rule is short: internal consistency is not evidence. A closed system can be perfectly consistent and entirely wrong, and the more thoroughly its figures derive from one another, the more confidently it will report a fiction. Any number a business acts on needs at least one check whose reference point sits outside the system that produced it.

The postmortem

The fix took minutes. One configuration path, one dead setting, one deploy. If the postmortem had ended there, the platform would have been protected against this campaign and nothing else.

So the postmortem asked three questions instead:

  1. What did the system believe, and where did the belief come from? Not “what was the bug” — what proposition was the platform asserting, and which input was it asserting it from.
  2. What made the belief unfalsifiable? Every check that existed had passed. That is a property of the check design, not bad luck, and it needed to be named as a defect in its own right.
  3. What property should this system have that would have made the error impossible, or made it visible on day one?

The third question is the one that produces durable output. A fix protects the campaign that broke. A property — an invariant, stated as something that must always be true of the system rather than something someone must remember to do — protects every campaign that has not been configured yet, including the ones configured by people who never read this postmortem.

That postmortem produced fifteen design invariants.

The resulting design invariants

What follows is a summary of the categories those invariants fall into, written now, in public, for a reader who wants to know how the thinking went. It is not a verbatim reproduction of the list, and I am not going to dress a paraphrase up as a quotation.

Settlement-term consistency. There is one definition of a billable event per campaign, it resolves from the commercial terms, and billing, payout, reporting, and bidding all read that same resolution. If two code paths can answer “did this call convert?”, they are a defect regardless of whether they currently agree — because agreement that is not structural is agreement that expires.

Economic-inversion detection. The platform must be able to notice when money is moving in a direction the business model forbids: cost accruing against a population of calls that cannot generate collectible revenue. That is a question the system can ask about itself continuously, and it was one nobody had thought to ask.

Unearned-payout detection. Publisher payout is contingent on a buyer-side qualifying event. Contingency that is assumed at accrual time and never re-checked is not contingency; it is a default. It has to be verifiable after the fact against the term that actually applied.

Configuration provenance. Any setting capable of influencing money has exactly one live source, and superseded settings are removed rather than left in place for a forgotten branch to read. A legacy value that still parses is indistinguishable from a current one at runtime.

External reference. Every financial claim of consequence gets at least one check whose reference point is outside the platform — the contract, the buyer’s own acceptance, the bank.

Refusal semantics. Where the inputs are insufficient to compute a financial value correctly, the system produces an explicit “cannot be determined” rather than a default, a zero, or an interpolation. This one was written as a consequence of the others, and it is the invariant that turned out to matter most later.

Controls implemented in DCE

Invariants that live in a document decay. The ones that survive are the ones that became something that runs.

Production monitoring in DCE now includes controls for phantom revenue, economic inversion, unearned payout, and settlement-term mismatch. In plain terms, the platform continuously asks itself:

  • Is there booked revenue whose underlying call does not satisfy the buyer’s settlement term?
  • Is there a population of calls where accrued cost cannot be matched by collectible revenue?
  • Is publisher payout attached to calls whose buyer-side qualification never actually occurred?
  • Does the term used at settlement equal the term recorded on the campaign’s commercial configuration?

The important design decision is where these live. They are not unit tests. A unit test would not have caught this incident, because the code was doing precisely what it had been told to do; the defect was in the state of a running system, not in a function’s behaviour on an input someone had already imagined. Tests defend against the failures you can enumerate before shipping. Controls watch the system you actually ended up with, including the parts of it that were configured by someone else, last year, for a reason nobody wrote down.

I defined these controls — what each one asks, what it compares against, what constitutes a violation, and what has to happen when one fires. Coding agents implemented the queries, the rule definitions, and the alerting under my direction; I validated them against production data and against the original incident’s records. They sit alongside the rest of DCE’s production alerting rather than in a separate financial-integrity silo, because an alert that lives somewhere nobody is already looking is a document with extra steps.

The later ambiguous-billing case

Some months later, the AI operations system that runs DCE’s monthly financial close met a line worth roughly $21.1K whose correct value could not be established from the available sources. The records that should have agreed did not, and none of them was authoritative for that particular line.

A system optimized to produce answers has several attractive options here. Take the platform’s figure as canonical. Apply the prior period’s rate. Interpolate from the surrounding lines. Every one of those produces output that is indistinguishable, in a spreadsheet cell, from a figure that was actually derived — same font, same alignment, same air of having been computed. And $21.1K is exactly the size of number that is large enough to matter and small enough to survive a review that is looking for large problems.

It refused. It declined to assign a value, named what it could not determine and what evidence would resolve it, and escalated to a human — me — rather than emitting a plausible number.

That behaviour is not an accident of the model. It is the operating rule: where the sources do not support a conclusion, the system escalates rather than estimating. The cost of the refusal was a person spending time on one line item during a close. The phantom-revenue incident is what the other policy costs, and the price of the other policy is not paid at the moment the guess is made. It is paid weeks later, by someone reading a report that has been quietly wrong the entire time.

Why refusal is a feature

The argument generalizes past billing, and it is the reason this incident is worth writing up rather than just fixing.

A system that always answers has told you nothing when it answers. If refusal is not in the output space, then “computed from sufficient evidence” and “produced because output was required” arrive in the same shape, through the same channel, with the same confident framing. The reader cannot distinguish them, so the rational response is to trust every output slightly less — which is a tax levied on all the correct answers by the possibility of the fabricated one. Refusal is what gives the other outputs their information content.

A default is a silent guess. Zero, null, last month’s value, and the surrounding average are all guesses that have been laundered into the shape of a fact. The moment a value is written into a cell, its provenance is gone; three steps downstream nobody can tell which numbers were derived and which were furniture.

Refusal has to be a first-class output, not an error. It needs a name, a structure, a stated reason, an enumeration of the missing evidence, and a route to the human with the authority to resolve it. “The system threw an exception” is not a refusal — it is a failure that a retry will paper over. A refusal is a considered result: this cannot be determined from what is available, here is specifically what is missing, here is who can supply it.

And it has to be rare and specific, or it becomes noise. A system that refuses whenever it is uncertain teaches its users to click through refusals, which is worse than not having them. The bar is not “I am not confident.” The bar is “the available evidence does not determine this value.” That distinction is a product decision, not a model parameter, and getting it wrong in either direction destroys the control.

The phantom-revenue incident and the $21.1K line are the same question asked twice. The first time, the system had no way to say I cannot determine this, so it said something else, and the something else reconciled beautifully with everything around it for as long as nobody checked it against the world. The second time, it could say it, and did.

The refusal was the feature.