基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-11-02 13:04

Stripe 回调遭遇 401,先查平台鉴权

dev.to · AI#Stripe#Supabase#故障排查
Editor brief · 编辑速览

Stripe 回调 Supabase Edge Functions 时,默认 JWT 校验可能在业务代码执行前拒绝请求,表现为 401 且函数日志为空。文章给出关闭该回调函数 JWT 校验的配置思路,并解释 Stripe 使用独立签名机制。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

Stripe 已经收款,用户却迟迟拿不到 Pro,问题可能出在业务代码执行之前。原文定位了一类 Supabase Edge Functions 故障:默认 JWT 校验先拦住 Stripe 请求,返回 401,函数日志因此空空如也。

修复需要同时处理两件事:关闭 webhook 函数的平台 JWT 校验,并在代码里严格验证 Stripe 签名。这篇最有用的地方,是把入口配置、签名验证和事件重试串成了一条可以照着排查的路径。

收款成功,为什么函数日志还是空的

原文给出的典型症状是:Checkout 正常,Stripe 里能看到付款,但用户权益没有更新。在 Stripe 的 Developers → Webhooks → 对应端点 → Event deliveries 中,每次投递都返回 HTTP 401,响应为 Missing authorization header 或 Invalid JWT。

401 加上空日志,是请求可能被平台提前拦截的线索。

Supabase Edge Functions 默认启用 verify_jwt,在函数执行前检查 Authorization: Bearer <Supabase JWT>。已登录的前端通过 supabase.functions.invoke() 调用时,会携带相应认证信息。

Stripe 没有你应用的登录令牌。它使用 webhook secret 对请求签名,再把签名放进 Stripe-Signature 请求头。两种认证机制对不上,请求就进不了函数。原文称,live mode 下 Stripe 会持续重试,最长可达 3 天,但重试解决不了入口配置错误。

作者认为,Lovable、Bolt 等 AI 构建工具容易生成处理逻辑正确、部署配置却仍为默认值的 webhook。因此,检查生成的代码只是排查的一部分,还得看平台配置和 Stripe 投递记录。

关闭 JWT 校验,要把范围限定到 webhook

原文优先推荐把设置写进 supabase/config.toml,让配置进入仓库并随重新部署保留:

[functions.stripe-webhook]
verify_jwt = false

注意第一行的 stripe-webhook 必须对应实际函数目录名。修改后执行 supabase functions deploy stripe-webhook。

临时也可以运行 supabase functions deploy stripe-webhook --no-verify-jwt。但作者提醒,该参数只作用于本次部署;后续部署若没带参数,配置文件也没有明确设置 false,401 可能再次出现。Dashboard 中也可以进入 Edge Functions → 对应函数 → details,关闭 JWT verification。

如果由 Lovable 或 Bolt 部署,可以明确要求它只添加上述函数配置,并保留 Stripe 签名检查。部署结束后,再去 Dashboard 确认开关状态,最后到 Stripe 对失败事件点击 Resend,检查是否返回 200。

这里不能顺手关闭所有函数的校验。原文要求,像 create-checkout-session 这种供已登录用户调用的函数,通常仍应保留 verify_jwt。

作者另提到 sb_publishable_ 密钥的例外:相关函数如果也出现 401 Invalid JWT,可按其转述的 Supabase 文档关闭平台检查,并在函数内验证用户。原文没有展开具体适用条件,不能把这个例外理解成所有接口都可以取消认证。

签名、幂等和失败响应,要一起补齐

关闭平台 JWT 校验后,知道 URL 的人都能向 webhook 发起 POST。此时若直接相信请求体,攻击者就可能伪造 checkout.session.completed,骗取 Pro 权益。

下面保留原文的签名验证示例,它解决的是 Deno 环境中如何验证请求,再进入事件处理逻辑:

// supabase/functions/stripe-webhook/index.ts
import Stripe from "npm:stripe@^23";

const stripe = new Stripe(Deno.env.get("STRIPE_SECRET_KEY")!, {
  httpClient: Stripe.createFetchHttpClient(),
});
const cryptoProvider = Stripe.createSubtleCryptoProvider();

Deno.serve(async (req) => {
  if (req.method !== "POST") return new Response("Method not allowed", { status: 405 });

  const signature = req.headers.get("stripe-signature");
  if (!signature) return new Response("Missing signature", { status: 400 });

  const body = await req.text(); // 在任何 JSON 解析之前读取原始文本

  let event: Stripe.Event;
  try {
    event = await stripe.webhooks.constructEventAsync(
      body,
      signature,
      Deno.env.get("STRIPE_WEBHOOK_SECRET")!,
      undefined,
      cryptoProvider,
    );
  } catch (err) {
    console.error("Stripe signature check failed:", (err as Error).message);
    return new Response("Invalid signature", { status: 400 });
  }

  switch (event.type) {
    case "checkout.session.completed": {
      const session = event.data.object as Stripe.Checkout.Session;
      if (session.payment_status === "paid") {
        // 在这里授予权限,使用 session.client_reference_id 查找用户。
      }
      break;
    }
    // customer.subscription.updated / deleted, charge.refunded, ...
  }

  return Response.json({ received: true });
});

重点看 await req.text() 和 constructEventAsync:不能先调用 req.json() 再重新序列化,否则内容可能不再匹配签名;Deno 中使用 SubtleCrypto provider 时,同步 constructEvent 会抛出 SubtleCryptoProvider cannot be used in a synchronous context。

密钥也必须匹配当前端点。Stripe CLI 的 stripe listen、test-mode 端点和 live 端点各有自己的 whsec_...。部署环境用 supabase secrets set STRIPE_SECRET_KEY=sk_test_... STRIPE_WEBHOOK_SECRET=whsec_... 设置,别写进代码。

签名通过后还没结束。作者建议把 event.id 存入带唯一约束的表,跳过已处理事件;数据库写入失败必须返回 500,让 Stripe 重试。失败时仍返回 200,会让这次投递被当作成功。

本地验证按顺序执行 supabase functions serve --no-verify-jwt --env-file ./supabase/.env、stripe listen --forward-to http://localhost:54321/functions/v1/stripe-webhook、stripe trigger checkout.session.completed,并把监听命令输出的 secret 放进本地 .env。

还要反向测试:运行 curl -i -X POST https://<project-ref>.supabase.co/functions/v1/stripe-webhook -H "Content-Type: application/json" -d '{"type":"checkout.session.completed"}'。没有签名的请求应返回 400;如果返回 200,就说明伪造事件能被接受。

我的判断

这套排查路径成立的关键,是分清平台认证和支付方签名各自负责什么。把配置写进仓库,也比每次记住部署参数更容易审查和复现。

不过,原文没有展示投递截图、执行日志或对照实验。「AI 构建工具经常踩坑」属于作者的经验判断,没有数据支撑发生频率。空日志同样只是线索,还需要结合响应内容和实际配置确认原因。

代码也是骨架:授权部分只有注释,幂等表和数据库失败处理都没有实现。尤其不能简单地先记录 event.id,再授予权益,否则后一步失败后,重试可能被误判为已处理。原文没有说明这两步如何保持一致,这是落地时要补的部分。

作者另售 $14.98 的 Paywall Kit,并称其中包含完整测试版本;原文没有提供足够证据验证这个声明。免费内容已经能解释入口 401,但还不能当成完整支付系统直接上线。

能拿来做什么

  • 排查付款后权益未更新时,先查看 Stripe Event deliveries 的状态码和响应,再检查 Supabase 日志,用两边的信息判断请求卡在哪一层。
  • 使用 Lovable、Bolt 生成支付功能时,把函数级 verify_jwt 配置纳入代码审查,并在重新部署后确认 Dashboard 的实际状态。
  • 给 webhook 保留两类验证:合法 Stripe 事件能完成权益写入,无签名请求返回 400;再检查重复投递和数据库失败是否会造成漏授权或重复授权。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
接入外部大模型前,先查清个人数据流向
下一篇
语音 Agent 如何通过回放筛选自修复方案

资讯速览

dev.to · AI · 2026-11-02 13:04

AI 评分 8.0 / 10

相关标签

#Stripe#Supabase#故障排查

继续阅读

  • 模型表现不稳,先排查上下文与验证流程
  • 接入外部大模型前,先查清个人数据流向
  • 语音 Agent 如何通过回放筛选自修复方案
  • Claude 长会话闲置后可能触发缓存重写费
  • 生产级Agent网关如何组织身份与人工审批