Automate daily payment reconciliation across Stripe, PayPal, Square, and Bank ACH into a unified ledger, with intelligent matching (fees/refunds/splits/currency), discrepancy flagging, and optional QuickBooks Online journal sync.
Built as Solution #7 in my 30-Day Financial Automation Build Challenge: production-grade financial ops automations built in n8n + Postgres + Python (inside n8n code nodes).
Teams running multiple processors typically spend 10–20 hours/week manually matching transactions to accounting. That manual work creates:
- missed transactions
- duplicate postings
- fee mismatch confusion
- delayed cash visibility
This project gives you a repeatable, open-source reconciliation pipeline you can run daily.
-
Daily auto-pull from Stripe, PayPal, Square, and ACH sources
-
Unified transaction ledger (normalized schema across processors)
-
Intelligent matching engine
- refunds
- partial / split payments
- fee variations
- settlement vs auth timing
- currency conversion (via FX table)
-
Discrepancy detection
- missing transactions
- duplicates
- fee mismatches
- unmatched items (ledger vs books)
-
Exception queue for finance review
-
(Optional) QuickBooks Online sync
- auto-create journal entries
- track sync state + idempotency
-
Real-time cash position
- per processor account
- consolidated view
- Save 40+ hours/month
- Reduce reconciliation errors by ~90%
- Get reliable cash visibility across processors
┌──────────────┐ ┌──────────────┐
│ Stripe API │ │ PayPal API │
└──────┬────────┘ └──────┬────────┘
│ │
│ │
┌──────▼────────┐ ┌──────▼────────┐
│ Square API │ │ Bank / ACH │
└──────┬────────┘ └──────┬────────┘
│ │
└──────────┬──────────┘
│
┌───────▼────────┐
│ n8n Ingestion │ (pagination, rate limits, retries)
└───────┬────────┘
│
┌───────▼────────┐
│ Postgres │
│ - raw_events │
│ - normalized txns │
│ - unified_ledger │
│ - matches/errors │
└───────┬────────┘
│
┌───────▼────────┐
│ n8n Matching │ (Python code nodes)
└───────┬────────┘
│
┌───────▼────────┐
│ Exceptions Queue │ → Slack/Email + dashboard
└───────┬────────┘
│
┌───────▼────────┐
│ QBO Sync (opt.) │ → journals + sync_state
└──────────────────┘
.
├─ sql/
│ ├─ schema.sql
│ ├─ seed/
│ │ ├─ processors.csv
│ │ ├─ processor_accounts.csv
│ │ ├─ fx_rates.csv
│ │ ├─ raw_events.csv
│ │ ├─ normalized_transactions.csv
│ │ ├─ unified_ledger.csv
│ │ ├─ match_candidates.csv
│ │ ├─ matches.csv
│ │ ├─ discrepancies.csv
│ │ └─ qbo_journal_entries.csv
│
├─ n8n/
│ ├─ workflows/
│ │ ├─ 01_ingest_stripe.json
│ │ ├─ 02_ingest_paypal.json
│ │ ├─ 03_ingest_square.json
│ │ ├─ 04_ingest_ach_import.json
│ │ ├─ 05_normalize_enrich.json
│ │ ├─ 06_reconcile_match_engine.json
│ │ ├─ 07_qb_sync.json
│ │ └─ 08_exception_notifications.json
│
├── docs/
│ ├── architecture.md
│ └─ screenshots/
│
└── .env.example # Template for environment variables (API keys, DB connection, etc.)
├─ LICENSE
└─ README.md
-
Postgres 14+ (local, Supabase, Railway, RDS—anything Postgres)
-
n8n (self-hosted or n8n cloud)
-
Optional integrations:
- Stripe API credentials
- PayPal API credentials
- Square API credentials
- QuickBooks Online (OAuth app)
Run the schema:
psql "$DATABASE_URL" -f db/schema.sqlIf you want a demo quickly, import the CSVs under db/seed/ into their matching tables.
Tip: If you’re using n8n Tables, you can upload the CSVs directly there as well.
Copy .env.example → .env and set values.
Key variables:
DATABASE_URLN8N_BASE_URL(optional)
Processor API creds (optional for live pulls):
STRIPE_SECRET_KEYPAYPAL_CLIENT_ID,PAYPAL_CLIENT_SECRETSQUARE_ACCESS_TOKEN
QuickBooks Online (optional):
QBO_CLIENT_ID,QBO_CLIENT_SECRET,QBO_REALM_IDQBO_REFRESH_TOKEN(or use n8n OAuth2 credential)
In n8n:
- Go to Workflows → Import from File
- Import workflows from
n8n/workflows/in order (01 → 09)
- Run 01–04 to ingest (or just load seed data)
- Run 05 to build the unified ledger
- Run 06 to generate match candidates + matches
- Run 07 to create discrepancy queue
- (Optional) Run 08 to sync journals to QBO
- Run 09 to compute cash position snapshot
This project uses a multi-pass reconciliation strategy:
-
Exact match pass
- same processor account
- same currency
- amount within tolerance (configurable)
- date window (e.g., ±2 days)
- same external reference (when available)
-
Fee-aware pass
- gross vs net normalization
- fee computed and compared to expected range
-
Refund & reversal pass
- links refunds to original charge
- handles partial refunds
-
Split / partial payment pass
- groups multiple ledger lines that sum to expected amount
-
Scoring + best-candidate selection
- each candidate match gets a score
- highest score wins (if above threshold)
- everything else becomes an exception
Full details: docs/matching-logic.md
The schema is designed for:
- raw ingestion (append-only)
- normalization (one row per canonical transaction)
- ledger (unified view for matching)
- matching + discrepancies (auditable reconciliation)
- sync tracking (idempotent journal creation)
Key tables:
processors,processor_accountsraw_eventsnormalized_transactionsunified_ledgermatch_candidates,matchesdiscrepanciesqbo_journal_entries,sync_state
See: db/schema.sql
Each workflow is modular and can be run independently.
- 01_ingest_stripe: pulls charges, refunds, payouts/fees; stores raw + normalized
- 02_ingest_paypal: pulls balances + transactions; stores raw + normalized
- 03_ingest_square: pulls payments/refunds; stores raw + normalized
- 04_ingest_ach_import: imports ACH batches (CSV/Bank export)
- 05_build_unified_ledger: normalizes into a single ledger table
- 06_match_transactions: generates candidates, scores, writes matches
- 07_discrepancy_queue: flags mismatches + creates review queue
- 08_qbo_sync_journals: creates journal entries in QBO; tracks sync state
- 09_cash_position_snapshot: computes per-processor + total cash position
A simple, compelling 3–6 minute demo format:
-
Problem (20s): “4 processors. Manual rec takes 10–20 hrs/week.”
-
Architecture (30s): show n8n → Postgres → matching → exception queue → (optional) QBO.
-
Live run (2–3m):
- trigger ingestion or show populated ledger
- run matching
- open exceptions (duplicates, fee mismatch, missing settlement)
-
QBO sync (30s): show journal entry created + idempotency
-
Cash position (20s): show consolidated cash position snapshot
Script: docs/demo-script.md
-
Use per-processor cursors and store them in
sync_statefor incremental pulls. -
Enforce idempotency using unique constraints on
(processor, external_id, event_type)at the raw level and(processor_account_id, external_transaction_id)at the normalized level. -
Always preserve raw payloads for auditability.
-
Consider adding:
- dead-letter queue for failed ingestions
- reconciliation tolerances per processor
- alerting thresholds (e.g., any discrepancy over $500 triggers immediate Slack)
- Add Xero journal sync
- Add NetSuite CSV export
- Add UI dashboard (simple web app or Metabase preset)
- Improve split-payment matching with subset-sum optimization
- Add automated FX pulls (ECB/OpenExchangeRates)
Contributions are welcome.
- Open an issue describing the bug/feature
- Include sample data (sanitized) if possible
- For workflow changes, export the updated n8n JSON and keep node names stable
MIT (see LICENSE).
This is an automation template and reference implementation. Always validate outputs with your accounting team and tailor the journal logic to your chart of accounts and reporting requirements.
Built by Ugo Chukwu — Financial Automation Engineer.
If you’re dealing with reconciliation, revenue leakage, payment recovery, or finance ops automation at scale, feel free to reach out.