Compensating Entries: Reversals, Returns and Recalls in the Ledger

Sometimes a payment that already completed needs to be unwound — the payer disputes it, the receiving bank sends it back, or a recall is requested for a mistaken transfer. In a double-entry ledger you never reach back and edit the original posting; that would destroy the audit trail and break the fundamental rule that the books always balance. Instead you post a compensating entry: a new, opposite transaction that cancels out the effect of the first. KibiPay models three flavours of this — reversals, returns and recalls — and they share one mechanism.
The compensating primitive
When money originally moved out, the ledger committed it by debiting the tenant's reserved balance and crediting the rail's float. To unwind that, the platform posts the inverse: a deposit that debits the rail float and credits the tenant, putting the value back. There's no special "reversal" ledger operation — the ordinary deposit primitive, the same one used when money legitimately arrives, is exactly the opposite of a commit, so it does the job. The original entry stays untouched in the history; the compensating entry sits next to it, and together they net to zero.
Idempotent by construction
Unwinding money must never happen twice — a double reversal would credit the tenant twice for one returned payment. So every compensating entry is keyed idempotently. The reversal posts with an external reference derived from the payment and the kind of compensation, for example reversal:{payment_id}:{kind}. If a retry posts the same compensation again, the ledger recognizes the key and returns the original transfer rather than creating a second one. This is the same exactly-once discipline the platform applies to payments, applied to their unwinding.
Three flavours, one model
Reversal, return and recall describe why the money is coming back, and the platform records the distinction while treating the mechanics uniformly:
- A reversal unwinds a payment we initiated — typically because it settled or was accepted and then needs undoing. There's a dedicated reversal workflow for this: it only acts on payments that actually moved (settled or accepted), posts the compensating deposit, sets the payment to
REVERSEDand emits apayment.reversedevent. - A return is value coming back from the scheme side — an unpaid direct-debit collection, a rejected credit. The payment moves to
RETURNED. - A recall is a request to claw back a payment already sent, often after a mistaken or fraudulent transfer. The payment moves to
RECALLED.
A single internal endpoint handles all three by kind, mapping return, reversal and recall to their terminal statuses, posting the compensating double-entry, and stamping the original payment. Those terminal states are exactly the ones the exceptions report watches, so an unwound payment automatically shows up for compliance review.
First-class records, not just status changes
A compensation is more than a flipped status — it's an event with its own history, and the platform stores it as one. A dedicated reversal record captures the kind, amount, currency, reason, an optional link to the dispute that triggered it, and — importantly — the ledger reference of the real compensating double-entry it produced. That means you can always trace from "this payment was recalled" to the exact ledger transaction that moved the money back, and from a dispute to the return that resolved it. When an operator upholds a dispute in the console, it's this machinery that fires, turning a decision into a traceable, balanced, idempotent reversal.
Why do it this way
The alternative — mutating balances or deleting entries — is how you end up with books that don't balance and an audit trail you can't defend. Compensating entries keep the ledger append-only and self-consistent: every movement, including every undo, is a posting you can point to. The original payment and its reversal both remain in the record, and the truth is the sum of them. In a system that has to answer to auditors and regulators, that traceability isn't a nicety — it's the whole point.
From a dispute to a balanced entry
The compensating machinery isn't an abstract capability; it's what fires at the end of a real operational process. When an operator upholds a dispute in the console, the platform doesn't hand-edit a balance — it invokes the return-or-recall path, which maps the operator's decision to a terminal status, posts the compensating double-entry, and writes a first-class reversal record linking the movement back to the dispute that caused it. That record carries the ledger reference of the actual double-entry, so the trail is complete in both directions: from the dispute to the reversal, and from the reversal to the exact ledger transaction that moved the money. Combined with the idempotent keying, that means the same upheld dispute can't accidentally reverse a payment twice, and an auditor can reconstruct precisely what happened and why. This is the difference between a system that can move money back and one that can account for moving it back — and in regulated payments, only the second is acceptable. Every undo is itself a first-class, traceable, balanced event, which is exactly the property a double-entry ledger exists to guarantee.