Projects / Demo

Stripe Payout Reconciliation

n8n demo · All data is synthetic

I built this as a small demo to work through a problem I know well from payments: proving that the money a processor says it paid out actually reached the bank. I spent years as a software engineer at i2c working on card programs, and now I work as a fintech/payments PM. In both roles, reconciliation is where the edge cases show up: deposits that land a few days late, fees taken twice, missing credits, duplicates.

I generated Stripe-style payouts, balance transactions, and a bank statement, then seeded each of those cases on purpose. Next I built an n8n workflow that matches payouts to bank lines by amount and a date window, labels each exception with a plain reason, and writes out a report and a short summary. A check script confirms that it catches exactly the cases I planted. All of the data is synthetic and this is a demo, not a production system, but it shows how I think about matching rules and exception handling, and why it's worth explaining every exception rather than just counting them.

How it works

A Stripe payout and the bank deposit it creates often don't line up cleanly. Deposits land a day or more after the payout date, fees get deducted twice, a deposit goes missing, or the bank shows the same credit twice. Finance teams usually catch this in a spreadsheet. This workflow does the matching in n8n and writes a report that flags each exception with a reason.

Manual Trigger → Read/Parse Payouts CSV → Read/Parse Balance Transactions CSV → Read/Parse Bank Statement CSV → Reconcile (Code) → [Report CSV, Summary .md]

Matching rules

Only positive bank credits whose description contains “STRIPE” are considered. Each bank line can be used once; the closest date wins.

MATCHED

The bank amount equals the payout amount and the bank date is within ±2 days of the payout arrival date.

TIMING_DIFFERENCE

The amount is exact, but the bank date is 3–7 days away from the payout arrival.

AMOUNT_OFF_BY_FEE

The bank amount differs from the payout by exactly the payout fee, within ±2 days.

MISSING_DEPOSIT

No bank credit found for this payout.

DUPLICATE_DEPOSIT

A bank line that repeats an already-matched amount within the date window.

UNMATCHED_BANK_CREDIT

A Stripe-labelled bank credit with no matching payout.

LEDGER_MISMATCH

The sum of charge net values in the balance transactions doesn't equal the payout amount.

Demo

A short screen recording of the workflow running in n8n on synthetic data.

Screenshots

From the planning board to the final output.

Miro board showing the reconciliation flow
The reconciliation flow in Miro
n8n workflow canvas
The n8n workflow canvas
Successful workflow execution in n8n
A successful workflow execution
The reconciliation report output
The reconciliation report output

Stack

n8n 2.8.4 (self-hosted via npm, SQLite), Node 20, Python 3 for data generation and checks. The file paths in the workflow are absolute; edit them if you move the folder.

Optional LLM step

The canvas has a sticky note marking where an AI node could go, for example the Ollama Chat Model node running a free local model. It would turn exception rows into plain-English notes. It isn't wired in, so the demo runs without any key.

Seeded test cases

The synthetic data includes deliberate mismatches to verify the reconciliation logic:

  • po_005 is missing from the bank statement
  • po_007 is off by exactly its fee
  • po_009 has a duplicate deposit
  • po_011 lands 4 days late (timing difference)

A verification script confirms the output reports exactly these cases.

Resources

This is a demo built with synthetic data. No real Stripe account, bank, or customer data is used, and no paid service or API key is needed.