Stories · Season 3 — Magpie · Episode 05
The Transfer That Needed Undoing
In which money leaves an account, never arrives anywhere, and someone has to put it back — correctly, once, and on the record.

The story
A customer moved £500 from their Magpie savings to an external bank. Magpie debited the savings account, handed the payment to the external rails, and the external rails — after a cheerful “accepted” — bounced it two hours later. Sort code typo. The money had left, and it had nowhere to be.
This wasn’t part of a big multi-step saga with a tidy compensation plan. It was a single step that, occasionally, needed reversing after the fact, when an asynchronous downstream said “actually, no.” Kemi’s first version of the fix was a one-off script: find the debit, add a credit, done. It worked. Then it was needed again the next week, and someone wrote a slightly different one-off script. Then a third person did the same, and one of those scripts credited the wrong account because it didn’t check the original transfer’s details carefully.
Now Magpie had a folder of bespoke “undo money” scripts, each subtly different, each run by hand under pressure, none of them recorded as a proper reversal in the ledger. Owen’s audit question was, predictably, the painful one: “For every reversal we’ve ever done, can you show me what it reversed, who ran it, and that it ran exactly once?” The honest answer involved the word “folder.”
Why this is hard the traditional way
Not every “undo” is part of a neat orchestrated workflow. Sometimes a single action needs a reliable, repeatable compensation on its own — triggered later, by an external signal, outside any saga. Teams handle these with ad-hoc scripts, and ad-hoc scripts are where consistency goes to die: each is a fresh chance to get the logic wrong, run it twice, or forget to record it.
The deeper problem is that “undo” is real domain behavior — a reversal is a thing that happened and belongs in the record — but treating it as a manual script makes it invisible and unrepeatable. There’s no idempotency (run it twice, refund twice), no audit trail, and no shared definition of what “reverse this transfer” actually means.
How Relay changes the ending
Relay has standalone courier activities: compensation logic defined as a proper, reusable unit you can invoke on its own — outside a saga — when something needs undoing. The reversal becomes a first-class operation, not a script: it’s recorded, it’s idempotent, and it does the same correct thing every time.
// "Reverse this transfer" is a defined activity, not a hand-written script.
public sealed class ReverseTransfer : ICompensatingActivity<TransferContext>
{
public Task CompensateAsync(TransferContext ctx) =>
// credits the original source, references the original transfer,
// and is safe to invoke more than once for the same transfer.
_ledger.RecordReversalAsync(ctx.TransferId, ctx.Amount, ctx.SourceAccount);
}
Now when the external rails bounce a payment, Magpie invokes one defined, audited, idempotent activity. It credits the right account because the reversal knows the original transfer. It can’t double-refund. And it lands in the ledger as a proper reversal with a clear lineage. Owen’s audit question — what was reversed, by what, exactly once — answers itself, because reversal is now part of the system instead of a folder of scripts.
What it costs you to ignore this
- Ad-hoc undo scripts drift apart. Each hand-written reversal is a new chance to credit the wrong account or run twice.
- Reversals vanish from the record. Manual fixes don’t show up as proper, traceable events — exactly what auditors and customers need to see.
- Idempotency is on the operator. “Did I already run this?” at 4 p.m. under pressure is how money gets refunded twice.
- You can’t reuse what isn’t defined. The same correction, reimplemented each time, never gets safer.