---
title: "Stripe 收款告警：钱有风险时让手机响起来"
description: "把 Stripe 的 Webhook 按事件轻重分级送到手机：收款成功安静推送，争议交易直接来电——因为那个有回应期限。"
date: 2026-09-09
author: Nooc
authorAvatarLink: /images/avatars/nooc.webp
authorLink: https://nooc.me
tags:
  - Stripe
  - 收款告警
  - Webhook 通知
  - 独立开发者
  - 收入监控
---

# Stripe 收款告警：钱有风险时让手机响起来

Stripe 什么都发邮件，也就等于什么都没通知你。一笔成功扣款、一次订阅续费失败、一个拒付争议，最后都变成同一个收件箱里长得一样的消息；等你翻到的时候，它们之间的区别已经没意义了。

解决办法不是发更多通知，而是让每类 Stripe 事件带上它真正该有的紧急程度：有人付款时安静推一条，续费失败时用时效性通知，而争议交易——那个自带回应期限的——直接打电话给你。

## 哪些 Stripe 事件真的需要你

不是每个事件都值得通知，值得通知的那些也不一样急。

| Stripe 事件 | 为什么重要 | 紧急级别 |
| --- | --- | --- |
| `payment_intent.succeeded` | 有人付款了。知道就好，从来不急。 | 普通 |
| `invoice.payment_failed` | 续费失败。在订阅真正失效前还有一个重试窗口。 | 时效性 |
| `charge.dispute.created` | 拒付争议。Stripe 会给出一个由卡组织设定的回应期限，错过就等于默认败诉。 | 来电 |
| `payment_intent.payment_failed` | 一次性支付失败。量大时有价值，单看一条很吵。 | 普通，或者干脆不看 |

值得一通电话的只有争议交易。其余的都可以等你自己拿起手机再说；而一个拖了一周才发现的拒付，那笔钱本来就留不住了。

## 第一步 —— 按紧急级别各建一个频道

在 Echobell 里建三个频道，因为紧急级别是按频道设置的：

- **Stripe · 收入** —— 普通通知，用于收款成功
- **Stripe · 扣款失败** —— 时效性通知
- **Stripe · 争议交易** —— 来电

分别从频道设置里复制 webhook URL，等下 Stripe 三个都要用。

顺手把每个频道的 **仅 POST** 打开。Stripe 一定是用 POST 发送的，开启后能避免链接预览和地址栏自动补全把频道误触发。

## 第二步 —— 在 Stripe 里添加端点

进入 Stripe 后台，**Developers → Webhooks → Add endpoint**，粘贴频道 URL，然后只勾选这个频道该处理的事件。

建议一个频道对应一个端点，而不是所有事件塞进一个端点。Stripe 允许按端点选择事件，等于把路由放在了 Stripe 那边，你的频道可以保持简单。

如果你更想只用一个端点，那就指向一个频道，然后用条件过滤：

```
type == "charge.dispute.created"
```

条件不需要写模板里那对 `{{ }}` 包裹。

## 第三步 —— 用模板把 payload 变成人话

Stripe 发过来的 JSON 大致长这样：

```json
{
  "type": "charge.dispute.created",
  "data": {
    "object": {
      "amount": 4900,
      "currency": "usd",
      "reason": "fraudulent",
      "status": "warning_needs_response"
    }
  }
}
```

所以频道模板要伸进 `data.object` 里取值：

**标题**

```
争议交易：{{data.object.reason}}
```

**正文**

```
{{data.object.amount}} {{data.object.currency}} 被发起争议
状态：{{data.object.status}}
```

**有个坑几乎人人都踩：** Stripe 的金额单位是该货币的最小单位。`4900` 是 49.00 美元，不是 4900 美元。Echobell 模板会原样代入这个值，所以要么在发送前先做除法，要么把货币写在后面、心里明白它是「分」。如果这个数字必须一眼就对，就把除法放在 Stripe 和 Echobell 之间的那一层里——一个 Worker、一个自动化流程，或者你自己的接口。

收入频道用同样的思路，字更少：

**标题**

```
+{{data.object.amount}} {{data.object.currency}}
```

**正文**

```
{{data.object.description}}
```

## 第四步 —— 不用等真实付款就能测

Stripe CLI 可以把真实结构的事件打到你的端点上：

```bash
stripe trigger payment_intent.succeeded
stripe trigger charge.dispute.created
```

上线依赖之前先跑一遍。这也是最快能看清「某个你以为一定存在的字段是 `null` 时模板会渲染成什么样」的方式。

## 这套方案没解决的问题

在把它放到收入链路上之前，有两个限制需要知道。

**Echobell 不校验 Stripe 的签名。** Stripe 会给每个 webhook 带上 `Stripe-Signature` 头，校验它需要端点密钥，而 Echobell 的 webhook 接口并不做这件事——也就是说，保护这个频道的只有 URL 本身。请把它当作密钥对待：保持仅 POST 开启，不要粘进公开仓库或者截图里。

你可以用条件加一层很弱的检查：

```
header["stripe-signature"] != ""
```

它能确认「有个东西发来了长得像 Stripe 的请求」，但确认不了「这就是 Stripe」。如果一条伪造的通知会造成真实损失（而不只是让你困惑一下），那就在前面放一个你自己的接口，在那里校验签名，再由它去调 Echobell。

**这是告警链路，不是账本。** Stripe 会重试失败的 webhook 投递，但通知本身不构成发生过什么的记录。对账请以 Stripe 为准，不要拿通知历史当依据。

## 常见问题

### Stripe 能直接打电话给我吗？

不能。Stripe 只发邮件和 webhook。要变成一通电话，得把 webhook 转给一个能打电话的服务。

### 争议交易的告警真能把我叫醒吗？

如果频道设成来电模式，能。它会以来电形式抵达，穿透专注模式和勿扰。详见[让关键告警穿透 iOS 专注模式](/zh/blog/how-to-bypass-ios-focus-mode-for-critical-alerts)。

### 合伙人能收到同样的告警吗？

可以。把频道分享出去，每个订阅者自己选通知类型。你可以负责接电话，同一个事件他那边只是一条安静的推送。

### 测试模式怎么办？

Stripe 测试模式的事件只会发给在测试模式下注册的端点。想看到测试流量就在两种模式下都注册频道 URL；也可以先只在测试模式下注册，等你信任这套设置了再切。

## 小结

三个频道、三个端点、每个一份模板。重点不是「你能收到 Stripe 的通知」——你本来就能。重点是一笔拒付不再和一张收据长得一模一样。

[下载 iPhone 版 Echobell](https://apps.apple.com/app/apple-store/id6743597198?pt=128151925&ct=blog-stripe-payment-alerts-zh&mt=8) 或[在 Google Play 获取](https://play.google.com/store/apps/details?id=one.echobell.echobellandroid)，然后执行一次 `stripe trigger charge.dispute.created`，故意让手机响一回。

---

## 相关内容

- [Webhook 触发文档](/zh/docs/webhook)
- [模板系统参考](/zh/docs/template)
- [频道条件参考](/zh/docs/conditions)
- [让关键告警穿透 iOS 专注模式](/zh/blog/how-to-bypass-ios-focus-mode-for-critical-alerts)
- [开发者的告警疲劳修复指南](/zh/blog/fix-alert-fatigue-developer-guide)
