沉默的 $800 MRR 杀手:我为何创建 BillingWatch

发布: (2026年3月27日 GMT+8 12:46)
5 分钟阅读
原文: Dev.to

Source: Dev.to

请提供您希望翻译的文章正文内容,我将把它翻译成简体中文并保持原有的格式、Markdown 语法以及技术术语不变。谢谢!

我注意到下降的那一天

它始于我几乎忽略的 Slack 通知:“Stripe: charge failed.”
一次收费失败——并不异常。我把它抛在脑后,继续写代码。

三天后,在查看仪表盘时,我看到 MRR 下降了超过 $800。这不是逐渐的下滑,而是一个悬崖。八位订阅者在我忙于开发新功能时悄然流失。收费失败一次又一次堆积,Stripe 重试仍然失败,而我使用的任何工具都没有触发一次警报。

于是,我开始构建 BillingWatch

缺失的模式

Stripe 仪表盘会显示事件,但不会告诉你何时出现模式错误。我遗漏了:

  • 重复收费 – 同一客户、相同金额,在 60 秒内。Stripe 重试非常激进;幂等键容易写错。
  • 收费失败级联 – 不是单张卡片失败,而是一小时内出现五次失败。这不是“坏卡”,而是卡片测试攻击。
  • 订阅失效 – 当订阅变为 past_due 时会触发 customer.subscription.updated。容易被过滤,却可能错过数天。
  • 负数发票异常 – 意外的抵扣导致负数项目。若是有意为之还好,若不是则是潜在的静默错误。

这些都不是可以直接订阅的单一事件。它们是跨事件的模式,而模式需要监控。

BillingWatch 架构

BillingWatch 是一个 FastAPI 服务,位于你的 Stripe webhook 端点之前。每个事件在你的应用处理之前都会通过检测引擎。

# webhook.py
from fastapi import FastAPI, Request
from billingwatch.detectors import run_all_detectors
from billingwatch.store import save_event
import stripe

app = FastAPI()

@app.post("/webhook")
async def stripe_webhook(request: Request):
    payload = await request.body()
    event = stripe.Webhook.construct_event(
        payload,
        request.headers["stripe-signature"],
        STRIPE_WEBHOOK_SECRET,
    )

    # Store first, then detect
    await save_event(event)
    alerts = await run_all_detectors(event)

    if alerts:
        await send_alerts(alerts)

    return {"received": True}

检测器是可组合的。每个检测器接受一个事件并返回一个警报列表(或无返回)。它们可以查看历史事件以发现模式。

示例检测器:重复收费

# detectors/duplicate_charge.py
import time
from typing import List

class DuplicateChargeDetector:
    async def detect(self, event: dict) -> List[Alert]:
        if event["type"] != "charge.succeeded":
            return []

        charge = event["data"]["object"]
        window_start = time.time() - 60  # 60‑second window

        recent = await get_charges(
            customer=charge["customer"],
            amount=charge["amount"],
            since=window_start,
        )

        if len(recent) > 1:
            return [
                Alert(
                    level="warning",
                    message=(
                        f"Duplicate charge detected: {charge['customer']} "
                        f"charged ${charge['amount']/100:.2f} twice in 60s"
                    ),
                    event_id=event["id"],
                )
            ]
        return []

在每个 charge.succeeded 事件上运行它,你将永远不会错过重复收费。

现实世界的隐形杀手

我与另外五位 SaaS 创始人交谈过;每个人都有不同的“隐形杀手”:

  • 一个 webhook 端点静默返回 200,但在内部抛出了异常。
  • 一个订阅在信用卡过期后仍保持活跃 90 天
  • 卡号测试攻击数周未被检测到。

BillingWatch 附带了针对最常见模式的检测器,但真正的价值在于,你可以为业务特定的故障模式编写自己的检测器。

Core Principle

监控事件,而不仅仅是指标。
指标告诉你出了问题。事件模式告诉你原因——并且常常在指标显示之前就捕获到它。

Open Source

BillingWatch 是开源的。如果你在任何规模上使用 Stripe,但没有监控你的 webhook 流以发现异常,你就是盲目操作。我之所以构建它,是因为我必须这么做。你也可能会——最好使用已经可用的东西。

BillingWatch on GitHub

0 浏览
Back to Blog

相关文章

阅读更多 »