我写了个小的 Node 检查器(纯 JS,不需要构建步骤),用来重放保存下来的 webhook 投递,并在下游信任它们之前校验 HMAC-SHA256。三个里有两个能过。来自 billing-partner 的 evt_2044 失败了,但同一个合作方的 evt_2051 用同一个密钥却能通过。
$ node main.js
staging evt_1001 ok
billing-partner evt_2044 FAIL (digest)
billing-partner evt_2051 ok
两个密钥上周都轮换过,所以我一开始怀疑是密钥过期。但那样的话 evt_2051 也过不了才对。我能看到的唯一区别是 2044 里的客户名带重音符号,叫 Zoë Ramírez。
所以我怀疑 payload 传进 createHmac 的过程中某处存在 utf8 和 latin1 的问题。我试过改 update() 的编码参数,完全没区别。
lib/verify.js 的核心部分如下。
const body = JSON.parse(delivery.raw);
// sign the payload, same as the partner docs describe
const payload = JSON.stringify(body);
const expected = crypto
.createHmac("sha256", secret)
.update(payload, "utf8")
.digest("hex");
合作方文档说签名是对“JSON payload”做的(我挺确定的),所以我不知道还该对什么做哈希。我漏了什么?为什么只有那一个事件有问题?
(toleranceSeconds 的重放检查还是 TODO,别管它。)
从英语翻译·查看原文I wrote a small Node checker (plain JS, no build step) that replays saved webhook deliveries and verifies the HMAC-SHA256 before anything downstream trusts them. Two of three pass. evt_2044 from billing-partner fails, but evt_2051 from the same partner passes with the same secret.
$ node main.js
staging evt_1001 ok
billing-partner evt_2044 FAIL (digest)
billing-partner evt_2051 ok
Both secrets were rotated last week, so I first blamed a stale secret. But then evt_2051 wouldn't have passed either. The only difference I can see is the customer name in 2044: Zoë Ramírez, with accents.
So I suspected utf8 vs latin1 somewhere in how the payload goes into createHmac. I tried changing the encoding passed to update(). No difference at all.
The core of lib/verify.js:
const body = JSON.parse(delivery.raw);
// sign the payload, same as the partner docs describe
const payload = JSON.stringify(body);
const expected = crypto
.createHmac("sha256", secret)
.update(payload, "utf8")
.digest("hex");
The partner docs say the signature is over "the JSON payload" (I'm fairly sure), so I don't see what else I should be hashing. What am I missing? And why only that one event?
(The toleranceSeconds replay check is a TODO, ignore it.)