This one started with me opening my Newton admin dashboard the same way I do every morning, sipping coffee, eyes still half-shut — and noticing the trial-conversion number hadn't moved. I knew it should have. Two customers had finished their 7-day trial the day before yesterday and Stripe should have charged both of their cards.
"Tim, can you check on these two? They should be paid by now but the dashboard still says trial."
One hour later — root cause found, fixed at Stripe, both customers backfilled into my system as paid, and a brand new dashboard feature shipped while we were at it.
This whole episode is a clean example of why having an AI agent that lives on your own server with eyes on Stripe, your database, and your code at the same time is fundamentally different from chatting with ChatGPT in a browser tab.
The Symptom — My Numbers Didn't Match Stripe
Newton is a managed AI server I sell. Customers pay through Stripe — 7-day free trial, then it auto-bills.
The normal flow is supposed to look like this:
- Customer adds card → trial starts
- 7 days pass → Stripe creates an invoice and charges the card
- Stripe fires an
invoice.paidwebhook to Newton - Newton flips
is_trial=0,status=active, sends a thank-you email, updates the Brevo email list - Admin dashboard's trial converted counter ticks +1
Except this morning the 30-day conversion counter still said 1. I knew for a fact two customers had just finished their trial.
I checked Stripe's dashboard directly — both customers had successful charges. Real money had hit my Stripe account. Around $60 worth of subscriptions.
Money in. System unaware. Funny way to start the day.
Tim Started at Stripe
I didn't tell my AI where to start looking. I just said "these two customers paid, the dashboard doesn't know about it, find out why."
Tim went to Stripe first because Stripe is the source of truth on payments:
- Pulled both subscriptions via API → status was
active(nottrialinganymore — good) - Pulled both invoices → both had
paid: truefrom a day or two ago - The money was real and reconciled on Stripe's side
So the bug was on the Newton side. Either it never received the webhook, or it received it and silently dropped it.
Tim then pulled the webhook endpoint config from Stripe directly:
GET /v1/webhook_endpoints/{id}
And the enabled_events array on the endpoint had only four entries:
checkout.session.completedcustomer.subscription.deletedcustomer.subscription.updatedinvoice.payment_failed
No invoice.paid.
Meanwhile inside Newton's main.py, the handler for invoice.paid existed and was correct. It would have flipped the trial flag, sent the thank-you email, synced Brevo, and ticked the dashboard. The logic was perfect.
Stripe just never sent the event. The faucet was closed.
The Cute Part Is — The Bug Was Silent Everywhere
What's scary about a bug like this is that nothing actually errors:
- Stripe doesn't error — it's following its config exactly. You didn't subscribe to that event, so it doesn't send it.
- Newton doesn't error — the handler never gets called, so there's nothing to fail.
- Admin dashboard doesn't error — its DB query matches its data perfectly. The DB is just wrong.
It's silent at every layer.
If I hadn't happened to glance at the dashboard and notice the number didn't match my mental model — this bug could have lived for months. Stripe would have kept charging customers' cards just fine, but every single one of them would have stayed flagged as "trial" in my system forever.
This is the same class of distributed-system bug I wrote about in the Brevo email sync rewrite — event-driven systems are fragile. Miss one event and the rest of the system runs along happily on a wrong assumption. And it rhymes with the time my AI chased down a misleading FB Marketing API error 2654 in 30 minutes — big platforms throw bad error messages, and an AI that reads docs end-to-end blows past the false trail fast.
3 Fixes My AI Shipped in One Hour
Fix 1: Subscribe invoice.paid on the Stripe webhook endpoint.
Tim called the Stripe API directly:
POST /v1/webhook_endpoints/{id} with an updated enabled_events array that included invoice.paid.
From that moment forward, every paid invoice fires the event, the existing handler does its thing, no more silent drops.
Fix 2: Backfill the two customers the system had missed.
Tim wrote a script called backfill_trial_conversions.py that imports Newton's main.py and calls the same helper functions the live webhook uses:
- UPDATE the subscription row (
is_trial=0,status=active) - Write an audit_log entry with a
backfilled: trueflag so I can spot these later if I ever audit the log - Send me a Telegram notification
- Send the thank-you email the customer was supposed to receive
- Move the customer from the Brevo trial list to the paid list
- Fire a Facebook CAPI Purchase event so my ad campaigns know these two converted (which matters a lot for how the algorithm optimizes)
Reusing the existing helpers instead of writing parallel logic is the right call here — it means the backfill behaves identically to the normal webhook path. There's no chance of drift between the two.
The detail I appreciated most: before running anything, Tim backed up the DB to /tmp/newton.db.backup-XXXXX in case we needed to revert. I didn't ask for that. It just did it.
Fix 3: Auto-refresh the admin dashboard every 30 seconds.
Halfway through, Tim said "hold on — I should fix one more thing while I'm in here. Even with the webhook fixed, your admin.html only calls loadDashboard() when the page first opens. If you leave the tab open and a customer pays mid-day, you still won't see it."
So it added an auto-refresh that runs every 30 seconds — visibility-aware, so it stops polling when the tab is in the background or the document is hidden. That's a small detail but it matters when you have a laptop closed and don't want a phantom tab pinging your API every 30 seconds for hours.
This is the part of having an AI agent rather than an AI chatbot that I find most valuable. It doesn't just do what I asked. It looks at the bigger picture and proposes the related fixes itself.
Verified Live Through the API
Once the backfill ran, Tim hit /api/stats live to verify:
- trial_converted_30d: 1 → 3
- trial_active: 3 → 1
- MRR: +$60 (the two customers I'd missed)
- 30-day conversion rate: 75%
Numbers matched Stripe exactly. The system was consistent again.
Both customers received the thank-you emails they should have gotten. Both were moved out of the Brevo trial list and into the paid list (so they wouldn't keep getting the "your trial is about to end" drip — same kind of thing I covered in the Brevo sync rewrite).
Facebook also got both Purchase events, so my ad pixel learned that those two clicks converted into paying customers.
3 Lessons I Wrote Down
1. Having a webhook handler in your code isn't enough — you also have to subscribe to the event on Stripe.
Every time you add a new webhook handler in code, you have to verify the enabled_events on the Stripe endpoint actually includes it. They're two different layers and they can drift apart.
Tim recorded this as a lesson in its own memory file: "Whenever I add a new Stripe webhook handler in code, verify the endpoint's enabled_events array includes the same event."
2. Event-driven systems need a reconciliation job.
Event-driven design is fragile by nature. Every system that depends on receiving events should also have a cron that periodically compares its state to the source of truth (Stripe, in this case) and self-heals any drift. I've already queued up the next task — a daily reconcile cron between the Newton DB and Stripe.
3. Backfill via existing helpers, never reimplement.
If your normal logic is correct, then the backfill is just the same function calls plus a backfilled: true flag in the audit log. Don't write a parallel implementation. It will drift, and then you'll have two truths instead of one.
Why I'm Telling You This
Three reasons, all relevant to anyone running their own business.
One — the SaaS tools you stitch together don't always behave the way the marketing pages claim, especially where two services hand off to each other. Bugs like this are everywhere. They just don't surface until something obvious breaks.
Two — without an AI agent that can see your code, your DB, your APIs, and Stripe at the same time, a bug like this takes days or weeks to track down. Tim took an hour, from the moment I asked the question to the moment the live API confirmed the fix.
Three — silent bugs are the most dangerous kind. You don't notice them until a metric looks off in the corner of your eye. Which is why a good admin dashboard (the one Tim built for me in one afternoon) is worth more than ten new features. (Right after this fix shipped, I noticed two cards on that same dashboard disagreeing — so my AI rebuilt the whole stats endpoint as a 5-stage funnel that has to sum to total or fail.)
Try Having an AI Agent on Your Own Server
When I told a friend this story, he asked "okay but what if I'm not a developer? How would I have an AI agent that can do this?"
The answer is Newton. You get a private AI agent on your own server, ready in 10 minutes, free for 7 days. It lives only on your server, sees only your code and your data, and is ready to chase down weird bugs across Stripe, your database, and your application code the same way Tim did for me.
And if my system ever drops a webhook on the day Stripe charges your card — there's still a 24-hour grace period before anything bad happens. Belt and suspenders.
— Pond
