# Stripe webhook 本地能过，部署到服务器就验签失败，会是时钟的问题吗？

**URL:** <https://www.shiqidu.com/t/topic/1871>\
**Category:** 问题求助\
**Tags:** 编程, API, Stripe\
**Created:** [2026 年10 月 31 日 13:22 UTC](https://www.shiqidu.com/t/topic/1871 "2026-10-31T13:22:43Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

作者： ![shiqidu\_editor](https://www.shiqidu.com/user_avatar/www.shiqidu.com/shiqidu_editor/32/528_2.png) [@shiqidu\_editor](https://www.shiqidu.com/u/shiqidu_editor)\
发布日期： [2026 年10 月 31 日 13:22 UTC](https://www.shiqidu.com/t/topic/1871/1 "2026-10-31T13:22:43Z")

</div>

本地用 `stripe listen` 转发时一切正常，同一份代码部署上线，验签却一个都过不去。从本机挪到服务器，中间真正换掉的东西没几样：请求多经过了一层反向代理或网关，框架的中间件配置可能不一样，签名密钥换成了线上端点那个，时钟也换成了另一台机器的。大家往往先怀疑时钟，可按 Stripe 排错页的写法，时钟排不到第一位。

## 密钥拿对了没有

Stripe 的 [webhook 排错页](https://docs.stripe.com/webhooks/signature)把用错签名密钥列为最常见的原因。`stripe listen` 在终端里打印的密钥，和 Workbench 里那个已注册端点的密钥不是同一个。同一个端点在测试和正式环境下，密钥也不一样。几种密钥都以 `whsec_` 开头，扫一眼很难分辨。把代码实际读到的值打出前几位，跟 Workbench 的 Webhooks 页里点 Reveal secret 看到的对一下，环境变量没切过来的情况一眼就能看出来。

## 请求体有没有被动过

签名是拿端点密钥，对「时间戳 + `.` + 请求体原文」这串字符算的 HMAC-SHA256。请求体多一个空格、键的顺序变了，结果就对不上。官方文档反复强调验签要用原始请求体：框架先把 JSON 解析成对象，你再序列化回字符串交给验签，那已经不是 Stripe 发来的那串了。

排错页点名的几个位置：

- Express 里 `app.use(express.json())` 要放在 webhook 路由后面，放在前面的话，请求体会先被解析掉。
- Next.js 的 Pages Router 要关掉 `bodyParser`，自己用 buffer 读原始内容。
- AWS API Gateway 转 Lambda 的，文档给了一个映射模板，把原始请求体放进 `rawBody` 再交给函数。

本地能过、线上不行，还要看线上是不是多了一层东西，比如网关统一挂了 JSON 处理、改了编码。另外 Rails、Django 这类框架默认带 CSRF 校验，可能让 webhook 请求根本走不到验签那一步。Stripe 文档建议给这个路由单独豁免。

## Header 有没有被改写

把收到的 `Stripe-Signature` 原样写进日志。正常的值是一整行，格式类似 `t=xxx,v1=yyy,v0=zzz`，其中 `v0` 只在测试事件里出现，正式环境只认 `v1`。拿到的值是空的、被截断了，或者读 header 的代码取错了字段，问题多半出在代理配置或取值逻辑上，跟签名算法无关。

## 再说时钟

`t=` 后面是 Stripe 发出这次请求时的时间戳。这个时间戳本身也算在签名里，中途改不了。[webhook 文档](https://docs.stripe.com/webhooks)里写着，官方库默认允许时间戳和服务器当前时间相差 5 分钟，超出就当作过期拒掉，用来防重放攻击。服务器时钟偏差超过这个范围，签名算得再对也通不过。文档的建议是用 NTP 保证服务器时间准确，跟 Stripe 那边同步。排查时可以在日志里同时记下 `t` 和服务器当前的 Unix 时间，一减就知道偏了多少。

容差能在验签时多传一个参数调大，但文档明确说不要设成 `0`，设成 0 等于把时效检查整个关掉。时钟不准就去修时钟，别靠放宽容差糊过去。

还有一个和时间有关的坑：Stripe 每次投递，包括重试，都会重新生成时间戳和签名。从日志里抠出一份旧请求、用 curl 重放到线上测试，过了 5 分钟肯定验不过，这说明不了代码有问题。想复现的话，在 Dashboard 里对具体事件点 Resend，或者用 `stripe trigger` 造一个新事件更靠谱。

站里之前那篇[护照加港卡能不能开 Stripe](https://www.shiqidu.com/t/topic/1791)聊的是开户资质，跟这里的接入排错是两回事。另一篇[支付宝异步通知验签失败](https://www.shiqidu.com/t/topic/387)讲的是别的支付渠道，排查时可以对照着看。

你们线上碰到的是哪一种？
