指南

什么是 Webhook?应用之间如何互相通知

Webhook 是一个自动消息,在某件事发生的那一刻,由一个服务发送到你的应用。当客户的付款成功时,Stripe 会向你应用中的某个地址发送一个 Webhook,内容是“付款成功,以下是详情”,然后你的应用做出反应:把订单标记为已付款、发送收据、解锁账户。简而言之,API 是你的应用向另一个服务提问;Webhook 则是另一个服务主动调用你的应用,告诉它一件事。

用 AI 建站时为什么它很重要

只要你的应用开始收款、接收其他工具提交的表单,或连接 GitHub、Shopify、日历等服务,你就会接触到 Webhook。它们通常是得知某件事已经发生的正确方式,但也是 AI 构建的应用最容易出现细微错误的地方:

  • 端点接受任何请求,因此任何人都可以伪造“付款成功”的事件。
  • 应用在结账跳转页面上授予访问权限,而不是依靠 Webhook,因此提前关闭标签页的人拿不到他们已付款的内容,而找到成功页面网址的人却能免费获得访问权。
  • 同一事件到达两次,客户收到两个订单或两封邮件。

了解 Webhook 的工作方式,可以让你提前提出这些保护要求。

日常类比:送货短信

你可以用两种方式知道包裹是否已送达。你可以每隔十分钟走到门口去看一眼(这叫轮询,即你的应用反复调用 API 询问“有新情况吗?”)。或者快递员在送达后给你发一条短信(这就是 Webhook)。

短信显然更好,前提是你能信任它。如果任何人都能给你发一条“包裹已送达”的消息,你会希望有办法确认它真的来自快递员。而且如果你送信时手机没开机,你也希望快递员之后再试一次。这两点——验证和重试——基本就是 Webhook 变得棘手的原因。

Webhook 的工作步骤

  1. 你在应用的后端创建一个端点:一个接受 POST 请求的网址,例如 https://yourapp.com/webhooks/stripe。
  2. 你向服务商注册这个网址,并选择你想接收的事件(例如“结账完成”和“订阅已取消”)。
  3. 服务商会给你一个签名密钥。 你把它作为密钥存放在服务器上,参见环境变量和密钥。
  4. 事件发生。 服务商向你的网址发送一个 HTTP 请求,请求体是描述该事件的 JSON,并附带一个签名头。
  5. 你的应用验证签名,记录事件 ID,迅速返回成功代码(200),然后再执行后续工作。
  6. 如果你的应用没有返回成功,许多服务商会稍后再次尝试。

签名

签名用于证明请求确实来自服务商,并且没有被篡改。例如,Stripe 会使用你的签名密钥,对请求体和时间戳计算一个 HMAC(一种带密钥的指纹,采用 SHA-256 算法),并把结果放在 Stripe-Signature 请求头中发送。你的应用用自己保存的密钥做同样的计算;如果结果一致,就说明请求是真实的。由于时间戳也参与了签名,你可以拒绝那些被人记录下来、之后又重放的旧请求——根据 Stripe 截至 2026 年 9 月的文档,其库默认的容差为五分钟。

一个实际细节:签名是基于原始请求体计算的。如果你的框架在验证之前就解析并重新格式化了 JSON,检查就会失败。这是一个非常常见的错误。

重试、重复与顺序

不同服务商的做法各不相同,请查阅各自的文档。截至 2026 年 9 月:

行为StripeGitHub
自动重试有,正式模式下最多三天,间隔逐渐拉长没有;你需要手动或通过脚本重新投递失败的请求
响应时间尽快返回 2xx,在任何慢速工作之前如果超过 10 秒仍未响应,该次投递视为失败

Stripe 的文档还指出,事件不保证按发生的顺序到达,同一事件偶尔也可能到达多次,因此建议记录事件 ID。

这一普遍原则适用于任何服务商:事件可能迟到、重复到达,并且顺序错乱。你的处理程序应当是幂等的——对同一事件处理两次,与只处理一次的效果相同。

实例:解锁付费课程

你在销售一门在线课程。学生点击购买,在 Stripe 的托管结账页面付款,然后被带回你的网站。

脆弱的做法: “感谢”页面直接解锁课程。如果学生在跳转途中关闭了浏览器,他们已经付了款却没有访问权限。如果有人猜到了感谢页面的网址,他们就能不付款获得访问权限。

使用 Webhook 的做法:

  1. Stripe 确认付款后,会向 /webhooks/stripe 发送一个 checkout.session.completed 事件。
  2. 你的后端用签名密钥验证签名。如果验证失败,就返回 400,不做任何处理。
  3. 它检查该事件 ID 是否已被处理过。如果是,就返回 200 并停止。
  4. 它保存事件 ID,返回 200,然后为学生开通访问权限并发送欢迎邮件。
  5. 感谢页面只显示“已收到付款——您的课程将很快出现”,并检查学生的访问状态。

之后,如果订阅被取消或银行卡扣款失败,其他 Webhook 事件会告诉你的应用取消或暂停访问权限。这就是订阅类产品与计费系统保持同步的方式,参见什么是 SaaS和如何为应用添加支付功能。

关键术语

  • 端点(Endpoint): 你应用中接收 Webhook 的网址。
  • 事件 / 负载(Event / payload): 发生了什么,以及描述它的数据(通常是 JSON)。
  • 签名密钥(Signing secret): 用于验证签名的共享密钥。
  • 幂等(Idempotent): 重复执行多次,结果与执行一次相同,因而是安全的。
  • 轮询(Polling): 反复向 API 询问变化,而不是等待 Webhook。
  • 隧道(Tunnel): 一种工具,在开发期间为你的笔记本电脑提供一个临时的公网网址,使 Webhook 能够访问它。

常见错误与误解

  • 不验证签名。 端点网址并不是秘密。验证才是让请求值得信任的原因。
  • 在回复之前执行耗时操作。 在响应之前发送邮件或生成 PDF,可能导致超时,超时又会触发重试,重试进而造成重复。
  • 假设事件恰好一次、按顺序到达。 应记录事件 ID,不要依赖到达顺序。
  • 使用预览网址。 Webhook 需要稳定的公网地址。私有预览和 localhost 无法从互联网访问;请使用你已发布的网址(或在测试时使用隧道)。
  • 混用测试和正式密钥。 服务商通常为测试模式和正式模式签发不同的签名密钥。请同时保存两者,并使用正确的那一个。
  • 对重要决策盲目信任负载。 对于高风险操作,在收到事件后,从服务商的 API 获取最新状态是合理的做法。

向你的 AI 建站工具提出什么要求

在服务器上添加一个 POST /webhooks/stripe 端点。使用原始请求体和密钥 STRIPE_WEBHOOK_SECRET 验证 Stripe 签名。对无效签名返回 400。保存每个事件 ID,并跳过已处理过的事件。快速返回 200,然后处理 checkout.session.completed:把订单标记为已付款并发送收据。记录事件类型。不要在结账成功页面上授予访问权限。

然后进行测试:从服务商后台发送一个测试事件,再发送同一个事件一次,以及一个签名错误的请求。只有第一次应当产生实际改动。

Mythex 中的 Webhook

Mythex 不运行单独的 Webhook 服务;你需要让 Agent 在你应用自己的后端中添加一个 Webhook 端点,并将服务商指向你的已发布网址,因为 Webhook 需要公网地址。签名密钥放入你的项目密钥中;如果你在线上应用中更改了它,就需要重新发布。文档中的处理 Webhook 方案提供了起始提示词,而店铺模板则是收款应用的良好起点。

常见问题

简单来说,什么是 Webhook?

Webhook 是一个自动消息,当某件事发生时(例如付款成功或表单被提交),一个服务会把它发送到你应用中的一个网址。你的应用不必反复询问是否有变化,而是由另一个服务主动告诉它。

Webhook 和 API 有什么区别?

使用 API 调用时,你的应用向另一个服务请求某些内容,并得到回应。使用 Webhook 时,另一个服务在事件发生时调用你的应用。许多集成会同时使用两者:用 Webhook 获知事件,用 API 调用获取详情或执行操作。

Webhook 安全吗?

只要你进行验证,就可以是安全的。设计良好的服务商会用一个只有你知道的密钥为每个 Webhook 签名,你的应用应拒绝任何签名不匹配的请求。如果没有这项检查,任何找到该网址的人都可以发送伪造的事件。

如果 Webhook 发送时我的应用宕机了,会怎样?

这取决于服务商。例如,Stripe 在正式模式下最多会在三天内对失败的投递进行重试,而 GitHub 不会自动重试,需要你重新投递。请查阅各服务商的文档,并设计你的处理程序,使其能够应对迟到或重复的事件。

Mythex · Pricing · 用 AI 构建任何东西 · 网站和应用模板 · Mythex 对比 · AI 应用构建术语表 · 免费的 AI 应用构建工具 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 繁體中文(香港) 繁體中文(台灣)