All articles
FintechOctober 2026 · 11 min read

Payment waterfalls: where lending software quietly breaks.

Every loan management system has a waterfall engine. Most of them are wrong in ways nobody notices for months. Here is what breaks, why it breaks quietly, and how we build one that survives partial payments, reversals and participations.

There is a component buried in every loan management system that decides, for each payment that arrives, where the money goes. Late fees first, then accrued interest, then principal — or some other order entirely, because the credit agreement says so. It is called the payment waterfall, and it is usually the smallest-looking piece of the system.

It is also the piece most likely to be quietly wrong. Not crash-the-server wrong. Wrong by $0.03 on a participation split, every month, across four thousand loans, until somebody reconciles a quarterly statement and the number does not tie.

We rebuilt the backend of Axis by AIO Logic, a commercial lending platform for middle-market lenders, from Java to C# on a CQRS architecture — migrating its money movement systems and delivering more than 61 financial reports along the way. Twelve of our engineers are still embedded on it. What follows is what that work teaches you about waterfalls.

Why waterfalls fail silently

A payment waterfall fails differently from the rest of your application. If your authentication breaks, nobody logs in and you know within minutes. If your waterfall misallocates, every screen still renders, every API returns 200, and the error compounds silently inside balances that look plausible.

By the time it surfaces it is usually through one of three routes: a borrower disputes a payoff quote, an investor queries a distribution, or an auditor pulls a sample. All three are expensive, and all three arrive months after the defect shipped.

The five cases that break naive implementations

Most waterfall engines are written against the happy path: a payment arrives, in full, on the due date, for a single-lender loan. That case is trivial. Here are the ones that are not.

1. Partial payments

A borrower sends less than the amount due. The waterfall has to consume the payment down the priority order and stop mid-tier, leaving a partially satisfied bucket. The next payment has to resume from exactly where the last one stopped — which means the remaining balance of each tier is itself state you have to store, not something you can recompute from the loan terms alone.

2. Payments that arrive out of order

A payment dated the 3rd lands in your system on the 11th, after a payment dated the 8th has already been applied. Interest accrues daily on a balance, so applying these in receipt order rather than effective order produces a different interest figure than applying them in effective order. One of those is correct and the other is a reconciling item nobody can explain.

3. Reversals and back-dated corrections

An ACH payment returns three weeks later. Now you have to unwind an allocation that has already triggered downstream effects: interest that accrued on a reduced balance, a fee that was waived because the account looked current, a distribution already paid out to participants.

This is the case that kills systems built on mutable balances. If the loan record holds only the current state, reversing means editing history in place, and you lose the ability to answer the one question an auditor will definitely ask: what did this balance look like on the 30th?

4. Rounding across participants

A $1,000 interest payment splits across three participants holding 33.33%, 33.33% and 33.34%. Round each share independently and you distribute $999.99 or $1,000.01. Over thousands of loans and twelve months, those cents become a reconciliation project.

The fix is not better rounding — it is allocating the remainder deterministically. Compute every share, floor them, then assign the residual pennies by a documented rule (largest fractional remainder, or a designated residual holder) so the allocation always sums to the payment exactly and the same inputs always produce the same output.

5. Fees that depend on the balance the payment is changing

A late fee calculated as a percentage of the outstanding balance, applied as part of the same waterfall run that is reducing that balance, is a circular dependency. Someone has to decide whether the fee is computed pre-application or post-application. Both are defensible; only one matches the credit agreement, and the system needs to make that an explicit, configurable decision rather than an accident of evaluation order.

What a correct engine looks like

Store events, derive balances

The single most useful structural decision is to stop treating the balance as the source of truth. Store the ordered sequence of financial events — advance, accrual, payment, allocation, reversal, write-off — and derive balances by folding over them.

This is why CQRS and event sourcing keep appearing in lending systems, and it is not architecture for its own sake. It buys you three things the business will ask for within the first year: point-in-time balances for any date, reversals as compensating events rather than destructive edits, and an audit trail that is the system of record rather than a log written alongside it.

Make the waterfall a declaration, not a function

The order of tiers belongs in data, not in a method body. Different credit agreements apply payments in different orders, and the ones that matter most are the exceptions. If adding a new allocation order means a code change and a release, that is a product constraint pretending to be a technical one.

  • Each tier declares what it consumes, in what priority, and up to what cap.
  • The engine walks the tiers; it does not know what a late fee is.
  • A loan references a waterfall definition by version, so historical runs can be replayed against the rules that applied at the time.

Make every run idempotent and replayable

Payment files get submitted twice. Jobs get retried. If applying the same payment event twice produces two allocations, you will find out in production. Key allocations on the payment event identity, not on the time the job ran, and make re-running a day a safe operation.

Test with invariants, not examples

Example-based tests cover the cases you thought of, which are by definition not the ones that break. The cases above are better caught by asserting properties that must hold for any input:

  • The sum of all allocations equals the payment amount, exactly, with no residual.
  • No tier balance is ever negative after a run.
  • Applying a reversal then re-applying the original payment returns the loan to its prior state.
  • Replaying the full event history from zero reproduces the current balance.
  • Allocation is deterministic: identical inputs produce byte-identical outputs.

Alongside those, golden-file tests against real amortisation schedules from the business will catch the interpretation errors that property tests cannot — the cases where the code is self-consistent and still disagrees with the credit agreement.

Migrating one without breaking money movement

Replacing a live waterfall engine is the part clients worry about most, and reasonably so. The approach that works is boring:

  • Run both engines against live traffic. Write both results, serve the old one.
  • Diff continuously. Every difference is either a bug in the new engine or a bug in the old one, and you need to know which before cutover.
  • Cut over per portfolio, not all at once, starting with the simplest loan structures.
  • Keep the old path switchable for a full reporting cycle — a quarter, not a sprint.

The parallel-run period is where the real defects surface, and it is where the temptation to shorten the schedule is strongest. It is also the cheapest place to find them.

The reporting tail

A waterfall engine is not finished when allocations are correct. It is finished when the reports built on top of it agree with each other. Delivering more than 61 financial reports on Axis made that obvious: every report is an independent assertion about the same underlying events, and a disagreement between two of them is a defect even when both look reasonable in isolation.

Treating reports as derived read models over the same event stream — rather than as separate queries against mutable balance tables — is what makes them agree by construction instead of by vigilance.

If you are building or fixing one

The pattern across every lending platform we have worked on is the same. The waterfall is under-specified at the start, because on the happy path it looks like three lines of arithmetic. The complexity is entirely in the exceptions, and the exceptions are not rare — they are the ordinary operating state of a loan book.

If you are building a lending platform, or you have one that produces numbers nobody can fully explain, tell us what it is doing. We have rebuilt this exact component under production load, and we will tell you plainly whether it needs a repair or a replacement.

Frequently asked questions

What is a payment waterfall in lending software?

A payment waterfall is the ordered set of rules that decides where each incoming dollar goes — late fees, then accrued interest, then principal, and onward through any participation or investor splits. The order is set by the credit agreement, and getting it wrong changes how much every party is owed.

Why are payment waterfalls hard to build?

Because the hard cases are the normal cases. Partial payments, payments that arrive out of order, reversals weeks after the fact, rounding across participants, and fee accruals that depend on a balance the payment itself is about to change. A waterfall that only handles payment-in-full on the due date will be wrong within a month of go-live.

Should a waterfall engine be event sourced?

For anything with reversals or participations, usually yes. If you store only the current balance you cannot answer what the balance was on a given date, and back-dated corrections force you to mutate history. Storing the ordered sequence of financial events and deriving balances from it makes both auditable.

How do you test a payment waterfall?

Golden-file tests against real amortisation schedules, property-based tests asserting invariants (allocations always sum to the payment, balances never go negative, reversal then re-apply returns the original state), and replay of historical payment files against the new engine compared line by line with the old one.

How do you migrate a lending platform without breaking money movement?

Run both engines in parallel against live traffic, write both results, serve the old one, and compare continuously until the differences are either zero or explained. Cut over per-portfolio rather than all at once, and keep the old path switchable for a full reporting cycle.

Need a Mendix team
that ships?