TL;DR
Revolut splits inflows and outflows across two columns instead of one signed Amount. FreshBooks marks invoices Paid based on its own logic — not what hit the bank. Match on amount + counterparty fuzzy name, validate with Date Paid ≤ Revolut date, and treat the Status = Paid flag as a hint not a fact.
What breaks when reconciling Revolut with FreshBooks
Three patterns that consistently bite:
The two-column amount problem. Revolut’s CSV uses Money in and Money out as separate non-negative columns. Almost every off-the-shelf reconciler assumes a single signed Amount field. If you don’t coalesce these before matching — signedAmount = MoneyIn - MoneyOut — every inflow row becomes ambiguous and your matcher either skips inflows entirely or treats outflows as positive inflows. We’ve seen both failure modes in real client files.
FreshBooks Status = Paid can lie. FreshBooks lets users manually mark an invoice as paid without recording a payment. The Status field shows Paid, but Date Paid is empty and there’s no payment record. If you trust Status alone, you’ll miss the actual bank-side cash event when it lands later. Always cross-check Status = Paid against either a non-empty Date Paid or a Payments report.
Empty Counterparty on real inflows. When a new payer sends you money via SEPA for the first time, Revolut sometimes records the Counterparty field as empty for the first transaction (until the payer is “saved” on Revolut’s side). The payer’s name is buried in the Reference field instead. Matching only on Counterparty misses these.
Export the Revolut statement
- Revolut Business web → Accounts → pick the account (each currency is a separate account).
- Statements → date range, then Export → CSV.
- File saves as
account-statement_YYYY-MM-DD_YYYY-MM-DD_undefined_xxxxxx.csv, UTF-8, comma-delimited.
Columns: Date started (UTC), Date completed (UTC), ID, Type, Description, Reference, Payer, Card number, Card label, Card state, Orig currency, Orig amount, Payment currency, Amount, Total amount, Exchange rate, Fee, Fee currency, Account, Beneficiary account number, Beneficiary sort code or routing number, Beneficiary IBAN, Beneficiary BIC, MCC, Related transaction id, Spend program.
Wait — newer exports use Amount and Fee as single columns (signed), older exports use the Money in / Money out split. We’ve seen both formats in the wild in 2026. Check the header row before assuming.
For inflows: Type = TOPUP (SEPA inbound) or Type = CARD_PAYMENT (card collection) or Type = TRANSFER (internal between Revolut accounts).
Export the FreshBooks invoice list
- FreshBooks → Reports → Invoice Details (under Accounts Receivable).
- Filter date range. Toggle on
Include partially paid. Status filter:Allis safest — we’ll filter downstream. - Export to CSV.
Columns you’ll get: Date, Invoice Number, Client, Description, Amount, Currency, Status, Outstanding, Date Paid, plus address fields and tax breakdowns.
Status values: Draft, Sent, Viewed, Partial, Paid, Disputed, Retainer, Auto-Paid. For reconciliation, Sent, Viewed, Partial, and Paid are all “issued and live” — only Draft and Disputed need exclusion.
Outstanding = Amount - sum(payments recorded). Outstanding = 0 and Status = Paid means FreshBooks thinks it’s settled; we still want to confirm against Revolut.
Match algorithm cheats for this combo
- Coalesce the amount columns. If the Revolut export has
Money in/Money outsplit, computesignedAmount = MoneyIn - MoneyOut. If it has a singleAmountcolumn with sign, use it directly. Detect by checking which header is present. - Multi-field counterparty resolution. For each inflow row, build a
payerNamefromCounterpartyif present, elseReference, elseDescription. Then strip transfer-prefix garbage like"From ","Transfer from ","SEPA Credit Transfer "before fuzzy-matching against FreshBooksClient. - Amount + 1-day date window. Match Revolut
signedAmountagainst FreshBooksAmountexactly first; fall back to ±$1 tolerance to absorb rounding. TheDate Paidfield in FreshBooks should be within 1 calendar day of Revolut’sDate completed— Revolut settles inbound SEPA same-day, card payments are T+1. - Card payments via Revolut. If
Type = CARD_PAYMENT, theDescriptioncarries the card-processor-side reference, not the client name. Match by amount + date window only; counterparty matching won’t work. - Distrust
Status = PaidwithoutDate Paid. If FreshBooks showsStatus = PaidbutDate Paidis empty, treat the invoice as still-to-be-confirmed. Look for the matching Revolut inflow; if found, the invoice is real-paid. If not, it was manually marked and the cash may never arrive.
Real example
Revolut inflow row (newer single-amount format):
2026-04-22 09:14:00,2026-04-22 09:14:13,trx_8h2k...,TOPUP,SEPA Credit Transfer,INV-2026-0418,Beta Marketing Ltd,,,,,,EUR,3240.00,3240.00,,,EUR,,,,,,,
FreshBooks invoice:
2026-04-01,INV-2026-0418,Beta Marketing Ltd,"Q2 retainer",3240.00,EUR,Sent,3240.00,
Reference exact match (INV-2026-0418), amount exact match (3240.00), counterparty exact match. Confidence: Exact. After matching, recommend marking this invoice paid in FreshBooks with Date Paid = 2026-04-22.
Edge cases this combo hits
Revolut fee aggregation. Cross-border SEPA transfers above £50k can incur a Revolut fee, charged as a separate row with Type = FEE, dated the same day. Pair fee rows with the parent inflow by Related transaction id (newer exports) or by amount-distance heuristic (older exports).
FreshBooks deposits. FreshBooks supports invoice deposits — a partial pre-payment that creates a separate transaction. The CSV row shows Status = Partial with Outstanding > 0. The bank-side will see two inflows for one logical invoice. Match each Revolut row to the cumulative partial-payments expected, not the full invoice amount.
Multi-currency FreshBooks. FreshBooks supports multi-currency but the CSV export shows Amount in the invoice’s currency. If your Revolut account is GBP but the invoice is USD-denominated, you’ll need a separate FX-aware match — the Revolut inflow shows the converted GBP amount, FreshBooks shows USD.
SumUp / Stripe via FreshBooks Payments. If your client clicks the “Pay this invoice” link in FreshBooks and pays via card, FreshBooks records the payment with Status = Auto-Paid and Date Paid = today. The cash arrives in your Revolut account ~2 business days later via Stripe payout. The amount will be net of Stripe fees (~2.9% + €0.30). Match on the net amount, not the gross invoice amount.
When this combo breaks our tool
We auto-detect Revolut’s two CSV format variants (single-amount vs split-amount columns), but only for English headers. If your Revolut account is set to French or German, the column headers are localized — the matcher won’t recognize them. Workaround: re-export with the language toggled to English.
FreshBooks invoices with custom line items longer than ~500 characters can wrap into the CSV across multiple rows. Our parser expects one row per invoice. Strip multi-line descriptions before importing.
If you’ve connected the Revolut → FreshBooks integration (the native bank-feed sync), FreshBooks will auto-match a subset of inflows to invoices. Our CSV-based matcher doesn’t know about FreshBooks-internal matches and may flag the same invoice as “Likely paid” even after FreshBooks has marked it Paid. Run one or the other, not both.
Faster way
Drop your Revolut CSV + FreshBooks invoice export into the reconciler on the homepage. The two-column amount format is auto-detected, the counterparty/reference fallback chain is built in, and the Status = Paid vs cash-event cross-check happens automatically.
If you need DSO context for the receivables you’re reconciling, the DSO glossary entry explains how to compute it from this same data.
If you want to switch off FreshBooks for invoice tracking entirely, the QuickBooks alternative post covers the same question for QB and the trade-offs apply equally to FreshBooks.