Stripe gives you payment_intent, charge_id, payout IDs, fees, and net movement. FreshBooks gives you invoice numbers, client names, paid dates, invoice totals, and payment status. Those fields can describe the same client payment and still share no reliable column for a direct match.

That is where the reconciliation breaks for consultants reviewing a client's month. The client says FreshBooks is paid. Stripe says the payment exists. The bank says a lower net deposit arrived two days later. None of those statements proves the invoice was matched correctly.

The file-level answer is to build the bridge yourself: identify which Stripe record is the payment, identify which FreshBooks row is the invoice or payment, decide whether you are matching gross invoice value or net payout cash, then produce an exceptions list that explains every unmatched row.

Know Which Stripe File You Have

Stripe exports can look similar on screen while answering different questions. A payments export, balance transactions export, payout report, and fees export should not be matched to FreshBooks in the same way.

Stripe file or viewWhat it showsFreshBooks file it can support
Payments or charges exportIndividual customer payments before payout settlementInvoice payments or paid invoice rows
Balance transactions exportGross amount, fee, net, refunds, and adjustmentsInvoice payments plus fee or adjustment review
Payout reconciliation reportThe batch that reached the bankBank or cash reconciliation, not invoice matching by itself
Fees exportProcessor fee rowsExpense posting or gross-to-net explanation
Refunds exportNegative payment activityCredit notes, refunds, or invoice corrections

The wrong export creates the wrong answer. If you compare a Stripe payout total to FreshBooks invoice totals, the file will look short because fees, refunds, and settlement timing sit between invoice value and cash. If you compare a Stripe payment export to the bank statement, the file will look high because the bank receives net payout cash, not gross customer payment rows.

For the FreshBooks invoice match, start with Stripe payment records. Bring in payout and fee data only after the invoice-level match is clear.

That distinction matters when the client infrastructure is mixed. One client may have a working Stripe connection. Another may export FreshBooks manually because the integration stopped syncing payment references. The consultant still needs a verification layer that works from the raw files.

Read the FreshBooks Export as Invoice Data, Not Cash Data

FreshBooks invoice exports are built around billing records. Depending on the report, the CSV may show invoice issue date, due date, paid date, client name, invoice number, status, total, balance, and payment details. Those are useful fields, but they do not mean the file is a cash ledger.

Start by separating invoice rows from payment rows.

FreshBooks fieldHow to use it in the match
Invoice numberStrongest FreshBooks-side reference when Stripe metadata contains it
Client nameSecondary check when no shared invoice number exists
Invoice dateBilling context only; not the cash match date
Paid dateUseful date for matching Stripe payment timing
Invoice totalGross amount expected from the client
Paid amountAmount to compare against Stripe payment value
Balance dueTells you whether a difference is a partial payment
StatusKeeps draft, unpaid, and void invoices out of the cash match
CurrencyRequired before comparing any amount

Do not compare every invoice in the export to Stripe. Filter to invoices that are paid, partly paid, or relevant to the period under review. Unpaid invoices belong in receivables. They do not belong in the Stripe payment match unless the question is whether FreshBooks missed a payment.

If the FreshBooks export is being compared to bank activity as well, the adjacent workflow is matching FreshBooks invoice CSVs to monthly bank statements. For Stripe-to-FreshBooks, stay one layer earlier: invoice payment to Stripe payment.

Build a Match Key From the Strongest Shared Evidence

The best match key is an invoice number stored in Stripe metadata. That happens when the payment was created from the invoice flow or when the client passed the invoice number into Stripe as a description, statement descriptor, or metadata field.

When it exists, use it first.

Stripe fieldFreshBooks fieldMatch strength
metadata.invoice_numberInvoice numberStrong
description containing invoice numberInvoice numberStrong if the text is consistent
Customer emailClient email or contactUseful, not enough alone
Customer nameClient nameUseful, weak with repeated clients
AmountPaid amountUseful, weak when amounts repeat
Created datePaid dateUseful inside a date window
Payment ID or charge IDPayment referenceStrong only if FreshBooks stores it

If the invoice number is missing from Stripe, build a composite key. A composite key combines fields that are not reliable alone but become reliable together.

Use this order:

  1. Invoice number or payment reference, if present.
  2. Client email plus amount plus date window.
  3. Client name plus amount plus date window.
  4. Amount plus date window plus description fragment.
  5. Amount-only matching only as a temporary candidate list.

Amount-only matching is not a finished reconciliation. Two clients can pay 1,500.00 in the same week. One client can pay two invoices for the same amount. A recurring advisory invoice can repeat every month. Amount narrows the field; it does not prove the match.

The date window also needs discipline. Stripe's created timestamp is when the payment event happened. FreshBooks paid date may be when the invoice was marked paid. The bank payout date is later. Comparing all three as if they were the same date creates false exceptions.

For invoice payment matching, allow a small window between Stripe payment creation and FreshBooks paid date. For payout-to-bank work, use the payout date and bank posting date instead. Those are different tests.

Normalize the Fields Before You Trust Any Lookup

The first pass should make both files comparable without changing the source records.

Create a working copy or helper columns for:

Helper fieldWhat it fixes
Normalized invoice numberRemoves spaces, casing differences, prefixes, and punctuation
Normalized client nameReduces spelling and casing differences
Client emailGives a more stable identifier than client name
Signed amountAligns Stripe and FreshBooks debit or credit signs
Amount basisLabels gross payment, fee, refund, partial payment, or net payout
Date windowPrevents false mismatches caused by one- or two-day timing gaps
CurrencyBlocks accidental matches across currencies

For Normalized invoice number, strip the prefix so "INV-1048" in FreshBooks and "Invoice 1048" in a Stripe description reduce to the same token:

Formula
=TRIM(SUBSTITUTE(UPPER(A2),"INV-",""))

Column A is the invoice number or Stripe description field. For Signed amount, convert text and remove sign direction:

Formula
=ABS(VALUE(B2))

Column B is the amount field. VALUE converts text amounts; ABS normalizes sign when Stripe shows positive charges and FreshBooks may show negative credits.

This is where a normal spreadsheet lookup often fails. It assumes the shared key already exists. In this workflow, it usually does not. The work is not "find the matching column." The work is "build enough evidence that two rows represent the same payment."

For example:

Stripe fieldValue
charge_idch_3R81K2
created2026-05-08 22:41:19 UTC
customer_emailops@exampleclient.com
descriptionPayment for Invoice 1048
amount2500.00
fee-76.50
net2423.50

FreshBooks might show:

FreshBooks fieldValue
invoice numberINV-1048
clientExample Client LLC
paid date2026-05-09
invoice total2500.00
paid amount2500.00
statusPaid

Those rows match by invoice number and gross amount. They do not match by exact date, and they do not match by net cash. If you compare Stripe net to FreshBooks paid amount, you create a fake difference of 76.50. That difference is the Stripe fee, not a missing invoice payment.

Separate Payment Matching From Payout Explanation

Stripe adds a second layer after the invoice is paid: settlement. The payment can match the FreshBooks invoice perfectly while the cash that reaches the bank is lower or later.

Keep the layers separate:

LayerStripe sourceFreshBooks sourceExpected result
Invoice paymentPayments or charges exportPaid invoices or payment recordsGross amount should match
Fee explanationBalance transactions or fees exportExpense posting reviewFee should explain gross-to-net gap
Refund or creditRefund export or balance transactionCredit note, refund, or adjusted invoiceNegative activity should be classified
Payout cashPayout reportBank statement or cash accountNet payout should match bank

If a client paid a 2,500.00 invoice by card, FreshBooks may show a paid invoice for 2,500.00. Stripe may show:

Stripe categoryAmount
Gross payment2,500.00
Stripe fee-76.50
Net to payout2,423.50

The FreshBooks invoice is not short. The cash deposit is net of fees. That is a payout or bank reconciliation question. The invoice match is already resolved.

This is the same separation used when matching a Stripe payout CSV to your bank statement: first prove the payment or payout being compared, then explain the transactions inside the cash movement.

Work the Exceptions in the Right Order

Once direct matches are removed, the remaining rows need labels. Do not leave them as "unmatched." That tells the client nothing and gives you no audit trail for the next month.

Use a practical exception sequence:

Invoice number exists in FreshBooks but not in Stripe.

Check whether the invoice was paid outside Stripe, marked paid manually, paid by bank transfer, or carried from another processor. If the payment route was not Stripe, remove it from the Stripe match and classify it separately.

Stripe payment exists but no FreshBooks invoice matches it.

Check whether the invoice number was missing from metadata, the client name changed, the payment was for a deposit, or the invoice was created after the payment. If no FreshBooks record exists, it is a real accounting exception.

Same client and amount, date outside the window.

Check UTC timing, manual paid-date edits, and month-end cutoffs. A one-day shift is usually timing. A two-week gap needs explanation.

Same invoice, different amount.

Look for partial payment, overpayment, discount, tax treatment, currency conversion, or a fee being netted incorrectly. Do not force the full invoice total to equal the paid amount when FreshBooks shows a balance still due.

Refund appears in Stripe but FreshBooks still shows the invoice as paid.

Check whether a credit note or refund was recorded. If not, the invoice match is incomplete because the payment was later reversed or reduced.

Duplicate candidates remain after matching.

Use invoice number first, then email, then description, then date sequence. If two rows still cannot be distinguished, leave them open as duplicate candidates rather than choosing one by feel.

The output should tell the next reviewer what happened:

StatusMeaningAction
MatchedStripe payment and FreshBooks invoice agree on reference and gross amountNo action
Timing differenceSame payment, date differs within expected windowNote timing
Fee explanationInvoice gross agrees, Stripe net is lower because of feePost or verify fee handling
Partial paymentStripe payment covers only part of the invoiceLeave balance open
FreshBooks-onlyPaid invoice has no Stripe paymentCheck other payment route or manual entry
Stripe-onlyStripe payment has no invoiceFind missing invoice or classify receipt
Refund missingStripe refund has no FreshBooks credit or refund recordRecord or investigate refund

A consultant does not need a beautiful workbook at this stage. They need a defensible answer: which invoices matched, which Stripe payments have no invoice, which FreshBooks paid invoices were not paid through Stripe, and which differences are timing, fee, refund, or partial-payment issues.

A Worked Example

Suppose the Stripe payments export shows:

Stripe paymentEmailDescriptionCreatedGrossFeeNet
ch_1001ops@northco.testInvoice 10482026-05-082,500.00-76.502,423.50
ch_1002ap@northco.testInvoice 10512026-05-101,200.00-37.201,162.80
ch_1003finance@eastco.testDeposit2026-05-12800.00-25.10774.90
re_2001ops@northco.testRefund INV-10482026-05-20-500.000.00-500.00

FreshBooks shows:

InvoiceClientPaid datePaid amountBalanceStatus
INV-1048North Co2026-05-092,500.000.00Paid
INV-1051North Co2026-05-101,200.000.00Paid
INV-1059East Co2026-05-121,600.00800.00Partly paid

The matches are:

FreshBooks invoiceStripe recordResultReason
INV-1048ch_1001MatchedInvoice number, client, and gross amount agree
INV-1051ch_1002MatchedInvoice number and gross amount agree
INV-1059ch_1003Partial paymentStripe payment covers 800.00; FreshBooks balance remains 800.00
INV-1048 refundre_2001ReviewStripe shows a later refund; FreshBooks needs a matching credit or refund record

The net amounts do not equal the invoice amounts because Stripe fees are present. That does not damage the invoice match. It creates a second task: verify that fees were posted and that the payout later agrees to the bank.

The finished note should read like this:

CategoryCountAmount
Matched paid invoices23,700.00
Partial payments matched1800.00
Stripe fees to verify3-138.80
Refunds needing FreshBooks review1-500.00
Unexplained difference00.00

That is a usable reconciliation result. It tells the client that the payment records match the invoices, one invoice remains partly paid, the fees are a separate posting check, and one refund needs FreshBooks treatment.

Use the Same Verification Layer Next Month

The durable process is not tied to one client's export layout. It is a sequence:

  1. Identify the Stripe file type.
  2. Identify whether the FreshBooks file shows invoices, payments, or both.
  3. Filter FreshBooks to paid and partly paid records.
  4. Normalize invoice numbers, client names, emails, dates, currency, and signs.
  5. Match by invoice number or payment reference first.
  6. Match by email, amount, and date window second.
  7. Separate gross invoice matching from Stripe fees and payout cash.
  8. Label every exception by cause.
  9. Produce a summary that another reviewer can understand without reopening both exports.

This is how independent consultants match Stripe payments FreshBooks invoices without depending on a client integration being configured correctly. The client can change their export columns. FreshBooks can omit the Stripe ID. Stripe can settle the payment two days later. The workflow still holds because it tests the business event, not a fragile single-column lookup.