Money moves in more places than your tests do.
In fintech, the costly bugs rarely crash anything. A retry posts twice, a balance drifts from its ledger, a payout leaves before a refund. Invarly checks the rules your money depends on, on live data, and shows the deploy that broke one.
Why fintech breaks quietly
A single customer action in a payments product touches more systems than almost anywhere else: your API, a payment provider, webhooks, a ledger, a balance cache, fee and FX logic, and a payout job that runs on its own schedule. Each one is correct on its own. The bugs live in the gaps between them.
Those gaps don’t throw errors. A duplicated transfer returns 200 twice. A drifting balance is still a valid number. Monitoring sees healthy services; tests see the happy path. The first signal is usually a reconciliation report, a support ticket, or an auditor.
Five places it breaks
Double posting after a retry
A payment provider times out, your service retries, and both calls succeed. Without an idempotency key enforced all the way to the ledger, one customer action becomes two transfers.
Balance drifting from the ledger
Most products keep a cached balance next to an append-only ledger. Every code path that writes one must write the other. One missed path, often a new admin tool or a migration, and the number the customer sees is no longer the number you owe them.
Payouts before refunds are netted
If a refund lands after the payout job reads its totals but before it pays out, the money leaves twice: once to the seller and once back to the buyer.
Your status and the provider’s disagree
Webhooks arrive late, out of order, or not at all. A payment captured at the provider stays pending in your system, or a failed one is marked paid.
Fees and FX leak cents
Rounding at the wrong step, or applying a quoted rate after it expired, loses a fraction per transaction. It never shows up in one order and always shows up at month end.
A walkthrough
A team adds automatic retries for failed transfers. The retry reuses the request but not the idempotency key. During a provider slowdown, 14 transfers post twice.
- all 14 wallets have two transfers with the same request id
- traced to 3c9e1aa “retry failed transfers”
payments/retry.go:57 - total over-credited € 2,318.40
The alert fires on the first run after the drift starts, lists every affected wallet, and points at the deploy that changed the retry path.
The starter rule pack
The rules most teams in this space start with. Each one is a query against your own tables, adjusted to your schema during setup.
| rule | protects | runs |
|---|---|---|
| wallet-balance-matches-ledger | Customer balances | 5 min |
| transfer-idempotent | Duplicate money movement | 1 min |
| payout-within-settled | Over-payouts to merchants | before each payout |
| payment-status-matches-psp | Webhook drift | 15 min |
| fx-within-quote | Rate and rounding leaks | 1 hour |
| no-negative-balance | Overdrafts | 5 min |
How it fits your stack
The agent runs next to your ledger database, usually against a read replica, and only sends results: which records broke a rule and the columns you choose to include. Card numbers and personal data never need to leave your network.
Deploys come from GitHub, alerts go to Slack or a webhook, and every check is kept with its result, so you have a record of what was verified and when.
Common questions
Do you need access to card or personal data?
No. Rules run on your own tables inside your network. You decide which columns, if any, are sent with a violation.
Can it check against our payment provider?
Yes, if the provider’s data is in your database, for example from webhooks or settlement files.
How often does a rule run?
From every minute to once a day, per rule. Money-moving rules usually run every one to five minutes.