You've got 30 affiliates driving sales. Every month you pull a report from Stripe, dump the numbers into a spreadsheet, calculate 15% commissions, and push a payout. By month three, your spreadsheet shows $47,000 in payouts due, but Stripe's settlement report says $42,800 actually flowed. By month six, the gap has widened to $56,000 owing versus $49,300 settled. You're not stealing from affiliates—you're losing track of where the math diverged. This isn't a uniquely spreadsheet problem. It's a timing and event sequencing problem. Stripe records a charge at one moment, a refund 72 hours later, a chargeback dispute 30 days later. Your spreadsheet recorded the charge and never saw the refund because you didn't manually check that day. By the time you reconcile, three months of edge cases have stacked: failed payments you retried, refunds that posted to different Stripe accounts, currency conversions on multi-region sales, disputed transactions that reversed mid-payout. For SaaS and agencies with 20+ affiliates, this drift becomes an operational liability. You either overpay and eat the margin loss, underpay and damage partner relationships, or spend 40 hours monthly in spreadsheet archaeology trying to find what happened in June. The fix isn't a better spreadsheet. It's automating reconciliation against Stripe's source of truth using webhooks and a simple database. Why spreadsheets diverge from Stripe settlements A spreadsheet is a point-in-time snapshot. You export a CSV on the 1st of the month. Stripe's reconciliation is an event stream. Every charge, refund, dispute, and chargeback is an event that affects the ledger. By the time your 1st-of-month export lands, Stripe has already processed refunds and disputes from the 25th onward that your spreadsheet never captures. The specific failure modes: Refunds after export. You export on June 1st. A customer disputes a charge on June 15th. Stripe reverses it on June 20th. Your June payout includes the original charge; your July reconciliation catches the refund. You've either double-counted commission or created a liability you have to manually claw back. Retried failed payments. Stripe retries failed card charges. A charge fails on June 5th, retries on June 8th, succeeds. Your spreadsheet may record both events, neither, or only the retry, depending on which CSV export you pulled and when. Multi-currency rounding. An affiliate in Singapore sells to a USD customer. Stripe converts at 1.35 SGD/USD and charges 0.5% forex fee. Your spreadsheet rounds to the nearest dollar. Stripe settles to the cent. Multiply by 100 transactions and you've got $300+ of unaccounted drift. Disputes and chargebacks. A customer disputes a transaction 45 days after purchase. Stripe deducts the charge, fee, and chargeback fee from settlement. Your affiliate commission spreadsheet has zero mechanism to retroactively adjust for this. Account-level fee variance. Stripe's settlement math includes platform fees, currency conversion, and occasional adjustments. Your spreadsheet calculates commission as a percentage of gross volume. Stripe nets it against fees. The reconciliation gap grows monthly. By month six, these edge cases have compounded into a 12–15% drift because you're reconciling against a spreadsheet designed for human intuition, not against Stripe's event-driven ledger. Build a Stripe webhook reconciliation system Instead of exporting CSVs, ingest Stripe events directly. Every time a charge, refund, or dispute occurs, Stripe sends a webhook. Store these events in a simple database table. At payout time, calculate commission from this ledger, not a spreadsheet. Step 1: Set up Stripe webhooks In Stripe Dashboard, create a webhook endpoint that listens for: charge.succeeded — A successful charge charge.refunded — A charge refunded in full or partial charge.dispute.created — A customer dispute filed charge.dispute.closed — A dispute resolved (in favor of customer or merchant) payout.paid — Stripe's settlement payout Point the webhook to a URL on your server (or a Lambda function if you're serverless). Stripe will POST a JSON payload every time an event occurs. Log each event with a timestamp, event ID, affiliate ID (stored in the charge metadata), and transaction amount. Step 2: Create a webhook ledger table Store each webhook event in a simple table: id | stripe_event_id | affiliate_id | event_type | amount | currency | metadata | created_at | processed_at 1 | evt_123abc | aff_001 | charge.succeeded | 15000 | USD | {"ref": "order_456"} | 2024-06-05 14:32 | 2024-06-05 14:33 2 | evt_124def | aff_001 | charge.refunded | -5000 | USD | {"ref": "order_456", "reason": "customer_request"} | 2024-06-20 09:15 | 2024-06-20 09:16 3 | evt_125ghi | aff_001 | charge.dispute.created | -15000 | USD | {"reason": "fraudulent"} | 2024-07-02 11:45 | 2024-07-02 11:46 Every webhook inserts a new row. Never update or delete rows—only append. This creates an audit trail. You can always replay the history and re