You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java将Map转回Shopify Webhook原始JSON实现HMAC校验

问题解答

核心前置说明

任何尝试将流转后的Map反向还原为Shopify原始请求体做HMAC校验的方案都无法保证100%成功率,HMAC校验对字节级一致性要求极高,只要序列化后的内容和Shopify原始发送内容存在1字节差异(比如键顺序变化、多余空格、数值格式变化、换行符差异),校验就会失败。官方推荐的标准方案是在Webhook请求入口层第一时间缓存原始请求字节流,后续业务层不管把数据转成Map还是实体类,都用缓存的原始字节做签名校验,从根源避免格式不一致问题。


1. Java端基于Map还原可校验JSON的兜底方案

如果受架构限制只能拿到流转后的Map数据,可按以下规则配置JSON序列化器,尽可能对齐Shopify的原始输出格式:

  • 前置要求:业务流转过程中存储Webhook数据的Map必须使用LinkedHashMap实现,禁止使用HashMap/TreeMap这类会打乱键插入顺序的实现,键顺序丢失的情况下完全无法还原原始格式。
  • 序列化器配置(以Jackson为例,这是Java生态和Shopify序列化规则对齐度最高的JSON库):
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.util.Map;

public class ShopifyWebhookVerifier {
    private static final ObjectMapper SHOPIFY_ALIGNED_MAPPER = new ObjectMapper()
            // 关闭格式化输出,不要添加任何缩进、换行空格
            .disable(SerializationFeature.INDENT_OUTPUT)
            // 保留Map原有键顺序,不要自动按键名字典序排序
            .disable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)
            // 不序列化值为null的键,和Shopify原始输出规则对齐
            .disable(SerializationFeature.WRITE_NULL_MAP_VALUES)
            // 大数值不使用科学计数法输出,避免ID类字段格式错误
            .enable(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN);

    /**
     * 从Map提取用于HMAC校验的字节数组
     */
    public static byte[] extractVerifyBytes(Map<String, Object> webhookMap) throws Exception {
        return SHOPIFY_ALIGNED_MAPPER.writeValueAsBytes(webhookMap);
    }
}
  • 参考最优实现(从入口层缓存原始流,彻底避免还原问题,SpringBoot场景示例):
@PostMapping("/shopify/webhook")
public ResponseEntity<?> handleWebhook(HttpServletRequest request) throws Exception {
    // 提前通过Request包装器缓存请求体字节,避免流被@RequestBody等逻辑消费后无法读取
    byte[] rawRequestBytes = ((CachedRequestWrapper) request).getCachedBytes();
    // 从请求头读取Shopify返回的签名
    String signature = request.getHeader("X-Shopify-Hmac-Sha256");
    // 直接用原始字节做HMAC校验,无需关心后续格式转换
    if (!hmac256Verify(rawRequestBytes, signature, WEBHOOK_SECRET)) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    }
    // 校验通过后再转Map/实体类做业务处理,转换逻辑不影响签名校验
    Map<String, Object> webhookData = new ObjectMapper().readValue(rawRequestBytes, Map.class);
    processBusiness(webhookData);
    return ResponseEntity.ok().build();
}

2. Shopify Webhook的编码与签名规则

  • 字符编码:所有Webhook请求体统一使用UTF-8无BOM编码,计算签名时直接取原始请求的字节数组,不会做任何编码转换。
  • 签名算法:采用HMAC-SHA256算法,签名密钥为Shopify后台Webhook配置页生成的专属Webhook Secret,和店铺API Access Token不是同一个值,注意不要混用。
  • 签名生成逻辑:Shopify服务端生成待签名内容时,直接使用待发送的原始HTTP请求体字节流,不会对JSON做任何预处理(不会调整键顺序、不会增减空格、不会格式化输出、不会过滤字段),签名计算完成后对二进制结果做Base64编码,存入X-Shopify-Hmac-Sha256请求头返回。
  • 注意事项:请求体的任何改动(包括末尾多一个换行、字符串多一个引号、数字类型转字符串、键顺序调换)都会导致最终计算出的签名和Shopify返回的签名不一致。

内容的提问来源于stack exchange,提问作者Shikha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 11:09:47