如何在CodeIgniter4中实现与Node/Express的JSON.stringify一致的处理以完成SHA256 Webhook验证
Webhook SHA256签名验证:CodeIgniter4与Node.js JSON序列化匹配方案
问题的核心在于Node.js的JSON.stringify和PHP的json_encode默认行为存在差异,加上你先解析请求体再重新编码的操作,导致生成的payload和Delyva用于签名的原始字符串不一致。下面是两个按优先级排序的解决方案:
方案1:直接使用原始请求体计算签名(最可靠)
Webhook签名通常基于发送方实际传输的原始JSON字节流计算,而非解析后再序列化的结果。你的Node示例代码虽然用了解析后再stringify的方式,但更准确的做法是直接拿原始请求体计算签名,完全避免解析和编码带来的差异。
在CodeIgniter4中获取原始请求体非常简单:
public function postTracking(): ResponseInterface { // Set your API secret $apiSecret = 'dx6ed8365ad507451595c151b6b61e3dbdcfbb987f'; // 直接获取原始请求体(不做解析) $rawPayload = $this->request->getRawInput(); // 从请求头获取签名 $signature = $this->request->getHeaderLine('X-Delyvax-Hmac-Sha256'); // 计算期望的签名 $expected = base64_encode( hash_hmac('sha256', $rawPayload, $apiSecret, true) ); // 签名验证逻辑 if ($signature !== $expected) { return $this->respond(['status' => 'error', 'message' => 'Invalid signature'], 401); } // 处理Webhook事件... return $this->respond([ 'status' => 'success', 'sign' => $signature, 'computed' => $expected, ]); }
这个方法直接使用Delyva发送的原始JSON字符串,完全匹配签名计算的数据源,是最不容易出错的方式。
方案2:调整json_encode参数匹配JSON.stringify行为(需解析数据时使用)
如果你必须先解析请求体再处理数据,需要让PHP的json_encode输出和Node的JSON.stringify完全一致的字符串。两者的核心差异可以通过以下参数修正:
JSON_UNESCAPED_SLASHES:不转义斜杠(和JSON.stringify一致)JSON_UNESCAPED_UNICODE:不转义Unicode字符JSON_PRESERVE_ZERO_FRACTION:保留浮点数的小数部分(比如14.54不会被转为14或14.540000)- 用
getJSON(true)解析为关联数组,而非stdClass,保证键顺序和原始JSON一致(PHP 7.3+默认保留关联数组的键顺序)
调整后的代码:
public function postTracking(): ResponseInterface { // Set your API secret $apiSecret = 'dx6ed8365ad507451595c151b6b61e3dbdcfbb987f'; // 解析为关联数组,保留原始键顺序 $parsedBody = $this->request->getJSON(true); // 按JSON.stringify的规则编码 $payload = json_encode( $parsedBody, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION ); // 从请求头获取签名 $signature = $this->request->getHeaderLine('X-Delyvax-Hmac-Sha256'); // 计算期望的签名 $expected = base64_encode( hash_hmac('sha256', $payload, $apiSecret, true) ); // 签名验证逻辑 if ($signature !== $expected) { return $this->respond(['status' => 'error', 'message' => 'Invalid signature'], 401); } // 处理Webhook事件... return $this->respond([ 'status' => 'success', 'sign' => $signature, 'computed' => $expected, ]); }
额外排查点
如果签名仍然不匹配,可以手动对比Node生成的payload和PHP生成的payload,重点检查:
- 键的顺序是否一致(
JSON.stringify保留插入顺序,PHP 7.3+关联数组也保留) - 浮点数格式是否一致(比如
14.54是否被转为14.540000) - 空对象/数组的处理(比如
personnel: {}是否被转为[]) - 是否有多余的转义字符
内容的提问来源于stack exchange,提问作者NM AA
相关产品推荐
相关产品推荐

