A technical deep-dive into Stripe payout reconciliation for engineering teams. Covers webhook event handling, payout ID mapping, fee and refund matching, multi-currency edge cases, and how to build a reconciliation pipeline that actually scales.
Every fintech that processes payments through Stripe hits the same wall. The first few months are fine: you check the Stripe dashboard, compare it to your database, and the numbers match. Then volume grows. You add subscriptions, marketplace payouts, multi-currency support, or Connect accounts. Suddenly, your bank statement says one number, your Stripe dashboard says another, and your internal ledger says a third.
This is not a Stripe problem. Stripe's ledger is internally consistent. The problem is the gap between Stripe's view of the world and yours. Stripe batches charges into payouts, nets fees, handles refunds asynchronously, and settles disputes on its own timeline. Your bank sees lump-sum deposits. Your product database sees individual transactions. Reconciliation is the work of proving these three views agree, and it gets harder at every order of magnitude.
This guide covers the architecture of a production-grade Stripe bank reconciliation pipeline. No hand-waving. We will walk through the data model, the matching logic, and the edge cases that will break your system if you ignore them.
Before writing any reconciliation code, you need to understand how money actually flows through Stripe.
When a customer pays, Stripe creates a charge object. That charge does not immediately land in your bank account. Instead, Stripe follows this sequence:
The critical insight: a single bank deposit corresponds to one Stripe payout, which itself aggregates dozens or hundreds of individual charges, refunds, and adjustments. Your reconciliation system must be able to decompose that bank line item back into its constituent parts.
The key API object for reconciliation is not the charge or the payout -- it is the balance transaction. Every movement of money in Stripe generates a balance transaction: charges, refunds, disputes, dispute reversals, transfers, payouts, application fees, and adjustments.
Each balance transaction includes:
This is your reconciliation rosetta stone. The sum of all balance transactions in a payout equals the payout amount. The sum of all payout amounts over a period should equal the sum of bank deposits for that period.
Polling Stripe's API works for small volumes. It does not scale. A production reconciliation pipeline should be webhook-driven with polling as a fallback for missed events.
The critical webhook events for reconciliation are:
Stripe webhooks are delivered at-least-once. Your ingestion layer must be idempotent. The standard pattern:
A common mistake is deduplicating on the webhook payload but not on downstream effects. If your reconciliation system creates a ledger entry for every charge webhook, and you receive a duplicate, you will double-count unless the ledger entry creation itself is idempotent.
Store the raw Stripe event payload before processing it. This serves two purposes:
Use a separate raw events table or event store. Do not mix raw ingestion with your reconciled state.
This is where reconciliation actually happens. You need to match records across three systems: your internal ledger, Stripe, and your bank. For a deeper treatment of matching algorithms, see how transaction matching works.
Match each Stripe balance transaction to a corresponding record in your system. This is typically a 1:1 match on a shared identifier:
The key fields to compare:
Once individual transactions are matched, decompose each Stripe payout into its constituent balance transactions:
Match each Stripe payout to a bank statement line item. This is often the hardest step because bank data is messy:
Real-world reconciliation is rarely clean 1:1. Common N:M patterns with Stripe:
For details on how a reconciliation engine handles these patterns at scale, see our architecture guide.
Stripe's fee structure varies by:
Do not hardcode fee percentages. Instead, always use the fee amount from the balance transaction object. Reconcile fees separately: your expected fee (from your rate card) vs. Stripe's actual fee (from the balance transaction). Discrepancies here are common and can add up to significant amounts at scale.
Disputes (chargebacks) are a reconciliation nightmare because they span multiple events and payouts:
Your reconciliation system must track the dispute lifecycle end-to-end and correctly associate the debit and potential credit with the original charge.
If you accept payments in multiple currencies, you have two reconciliation challenges:
Reconcile each currency separately. For FX conversions, track the conversion rate and timestamp. Accept that small rounding differences (sub-cent) are normal; define a tolerance threshold and auto-approve matches within it.
If you use Stripe Connect, the complexity multiplies:
Each pattern generates different balance transactions and requires different matching logic. Your reconciliation engine must be configured per-pattern, not one-size-fits-all.
A reconciliation engine that matches 99% of transactions automatically is useless if the remaining 1% requires manual investigation with no tooling. Exception management is where operational cost lives.
When a match fails, classify the exception:
Route exceptions to the right team or automated workflow based on classification:
Track reconciliation health metrics:
Building a Stripe reconciliation pipeline from scratch is straightforward for small volumes. But the engineering cost compounds:
For teams processing more than a few thousand transactions per month, the maintenance burden alone often justifies evaluating a purpose-built solution. NAYA's Stripe integration handles the ingestion, matching, and exception management out of the box, while keeping you in control of the rules and thresholds. See the full Stripe bank reconciliation use case for architecture details.
The question is not whether you can build it -- you can. The question is whether that is the best use of your engineering team's time.
Daily, at minimum. Ideally, your reconciliation runs continuously as events arrive. Batch reconciliation (weekly or monthly) creates a backlog of exceptions that becomes exponentially harder to investigate as context fades. A webhook-driven architecture with real-time matching gives you the fastest time-to-detection for breaks.
The most common causes: (1) timing -- the payout was created but has not yet settled, or settled on a different date than expected; (2) currency conversion -- your bank applied its own FX rate; (3) bank fees -- some banks deduct wire or deposit fees from incoming transfers; (4) Stripe reserves -- if Stripe is holding a reserve on your account, the payout amount may be reduced.
You need to reconcile at two levels: (1) the platform level, matching application fees and transfers against your platform's Stripe balance and bank account; (2) the connected account level, matching charges and payouts for each connected account. Use the balance transaction API filtered by type to separate platform fees from connected account activity. The transfer object links platform and connected account transactions.
Never calculate expected fees yourself for reconciliation purposes. Always use the fee field from the Stripe balance transaction as the source of truth. However, maintain a shadow calculation of expected fees based on your rate card so you can detect billing discrepancies with Stripe. Reconcile actual vs. expected fees monthly and raise any material differences with your Stripe account manager.
Track refunds as separate payment reconciliation records linked to the original charge. A charge can have multiple partial refunds over time, and each refund generates its own balance transaction that may land in a different payout than the original charge. Your matching engine must maintain a running tally of refunded amounts per charge and flag when cumulative refunds exceed the original charge amount.
The Stripe Sigma and Reports APIs are useful for ad-hoc analysis and can supplement your reconciliation pipeline. However, they are not a substitute for a real-time reconciliation engine. Reports are generated with a delay, do not cover bank-side matching, and do not provide exception management workflows. Use them as a secondary validation layer, not as your primary reconciliation mechanism.
Fintechs thrive on speed, but manual reconciliation causes costly delays, compliance risks, and scaling issues. Learn how automation and machine learning cut errors by 60%, unlock real-time insights, and turn reconciliation into a strategic advantage for growth and innovation.
Scaling fintechs face hidden risks and inefficiencies from outdated ledger systems. Discover how a purpose-built ledger streamlines compliance, reduces manual work, and unlocks real-time financial insights essential for sustainable growth.
Manual reconciliation is no longer viable. NAYA’s multi-agent AI platform transforms financial operations with 99%+ accuracy, dynamic rule generation, and real-time compliance monitoring. Discover how fintechs are replacing legacy systems with intelligent, scalable infrastructure.
Join 4,000+ fintech engineers receiving our best operational patterns.