Short answer: you compare your invoice list to your bank statement, line by line, and check off matches. This works fine if you have 20 invoices a month. It stops working around 50.

The boring manual method

Open your invoicing tool. Export the list of invoices to CSV. Open your bank’s online portal. Export the last month of transactions to CSV. Now open both in Excel side by side, sort by date, and start matching by amount.

For a freelancer doing a dozen client invoices a month, this takes 20 minutes and works fine. The friction starts when you cross 50 invoices, when amounts collide (two clients owe you $1,500), when bank fees deduct a few cents from the deposit, or when clients pay net of VAT and you have to back-calculate.

If you want the spreadsheet structure ready-made, download the invoice tracker template (11 KB, no signup) — status dropdown, outstanding amount, days outstanding, summary cards. Drop your data in.

What your invoicing tool already shows you (and why it’s wrong half the time)

FreshBooks, QuickBooks, Wave, Xero, inFakt, Fakturownia — every invoicing tool marks an invoice as “paid” if the client clicked the payment link inside the tool. That’s it. If your client paid you by bank transfer, Wise, Revolut, ACH, or any other channel, the invoice stays marked “unpaid” forever.

For B2B specifically, the share of manual bank transfers vs. payment-link payments depends on geography. In Poland and Germany, manual bank transfer is the default for B2B — somewhere around 80–90% of invoice payments. In the US and UK, payment-link share is higher, but anyone selling to mid-market or enterprise still gets paid by check, ACH, or wire transfer.

“$1M revenue, just paid $14k in tax penalties because we didn’t reconcile. Half the ‘unpaid’ invoices in QuickBooks were paid months ago by ACH.” — r/smallbusiness, March 2026

A faster way

You can do this with a script: download bank CSV, download invoice CSV, match amounts within ±3 days of the due date, allow ±0.50 USD/EUR for bank fees, and flag the rest. checkunpaidinvoices.com is that script with a web UI on top, plus aging buckets and anomaly detection. Free tier handles up to 50 invoices at a time.

The reason I trust the result: the matching logic is deterministic. The same two CSV files always produce the same output. There’s no AI deciding whether a transaction “looks like” your invoice — it’s amount + date + a simple fuzzy match on the counterparty name with a configurable threshold.

What the four buckets mean

The aging side — 0-30, 31-60, 61-90, 90+ days overdue — is purely calendar math against the invoice due date. If a client crosses 90 days, the statistical recovery rate drops below 30%, which is why the 90-day cliff is the line at which most B2B businesses stop sending reminders and start sending demand letters or referring to collections.

Skip the spreadsheet: drop your invoice list + bank statement CSV into the reconciliation tool. Four buckets back in 30 seconds, no signup.

Quick FAQ

What if a client paid me but used a different amount? We flag it as “likely paid” if the amount is within 5% and the name matches above 0.7 similarity. You see the match reason and can override.

What about partial payments? We detect two-transaction splits that sum to the invoice amount within the date window. Three-way splits we don’t handle — you’ll see them as “Unpaid” and have to mark manually. See partial payment for the matching logic.

Does this work with my bank / my invoicing tool? If we’ve seen the format before, instantly. If not, the parser does a 30-second wizard to map your columns once, then remembers for everyone.

Is this safe? I don’t want my financial data on a third-party server. All files are encrypted at rest and deleted within one hour. We never send your data to an AI model — the LLM we use only sees sanitized column-header samples to generate parser configs for unknown formats. Read Privacy for the full breakdown.