DEV Community

Ray
Ray

Posted on

How to catch Stripe billing bugs before they cost you money

Stripe billing bugs are sneaky. A missed webhook here, a duplicate charge there — and by the time you notice, customers are already filing chargebacks or you're processing partial payments without realizing it.

I built BillingWatch — a self-hosted FastAPI service that monitors your Stripe webhooks in real time and alerts you before small anomalies become expensive problems.

Here's how to set it up in under 15 minutes.

What BillingWatch catches

Before diving into setup, here's what it's actually watching for:

  • Failed renewals — subscription payments that silently fail with no retry
  • Duplicate charges — same customer, same amount, within a short window
  • Missed webhook events — Stripe sends but your endpoint never acknowledges
  • Unusual refund spikes — more than X refunds in a time period
  • Long-running invoices — invoices stuck in open state past their due date

Installation

git clone https://github.com/rmbell09-lang/BillingWatch.git ~/billingwatch
cd ~/billingwatch
pip install -r requirements.txt
cp .env.example .env
Enter fullscreen mode Exit fullscreen mode

Edit .env with your Stripe credentials:

STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_API_KEY=sk_live_...
ALERT_EMAIL=you@yourcompany.com
Enter fullscreen mode Exit fullscreen mode

Start the server:

uvicorn app.main:app --host 0.0.0.0 --port 8000
Enter fullscreen mode Exit fullscreen mode

Connecting your Stripe webhooks

In your Stripe dashboard:

  1. Go to Developers → Webhooks → Add endpoint
  2. URL: https://your-server.com/webhooks/stripe
  3. Select events: invoice.payment_failed, invoice.payment_succeeded, charge.dispute.created, customer.subscription.deleted

BillingWatch validates the webhook signature automatically using STRIPE_WEBHOOK_SECRET.

Anomaly detection in action

BillingWatch uses a sliding window approach — if more than N of the same event type occurs within a configurable time window, it fires an alert.

# From app/detectors/duplicate_charge.py
async def check_duplicate_charges(customer_id: str, amount: int, window_minutes: int = 5):
    recent = await db.get_recent_charges(customer_id, window_minutes)
    matching = [c for c in recent if c.amount == amount]
    if len(matching) >= 2:
        await alert.fire("duplicate_charge", customer_id=customer_id, amount=amount)
Enter fullscreen mode Exit fullscreen mode

You can tune the thresholds in config/rules.yaml — no code changes needed.

Alerts

Out of the box, BillingWatch supports:

  • Email alerts (SMTP)
  • Webhook to Slack/Discord
  • Writing to a local log file

For self-hosted setups without a mail server, the Slack webhook is the easiest path.

Why self-hosted?

I wanted full control over the data — billing anomalies often contain sensitive customer info. Running BillingWatch on your own infra means no third-party SaaS has visibility into your Stripe events.

It also costs nothing beyond your server.

GitHub

github.com/rmbell09-lang/BillingWatch

PRs welcome. If you catch a billing bug with it, I'd love to hear about it.

Top comments (0)