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

如何在AWS Lambda中验证Facebook WhatsApp Webhook的Payload?

解决Facebook Webhook messages字段签名验证失败问题

问题核心

按照Facebook Webhook签名验证规范实现后,大部分事件字段验证通过,但messages字段始终验证失败,本质原因是生成签名时使用的payload与Facebook签名时的原始payload存在细微格式差异(比如转义方式、JSON结构、Unicode处理)。

解决方案

1. 直接使用原始请求体生成签名

当前代码中对event['body']执行了json.dumps(payload),这会把已经是JSON字符串的body再次转义(比如原始{"text":"你好"}会变成"{\"text\":\"你好\"}"),完全偏离了Facebook的原始payload。

修正代码片段:

import hmac
import hashlib
import os

def lambda_handler(event, context):
    response = {
        "status": 500,
        "body" : "failed"
    }

    try:
        signature = event["params"]["header"]["X-Hub-Signature-256"]
        if not signature:
            return "couldn't find signature in headers"
        
        signatureHash = signature.split("=")[1]
        app_secret = os.environ.get('APP_SECRET')
        if not app_secret:
            return "APP_SECRET not set"
        
        key = bytes(app_secret, 'UTF-8')
        # 直接使用API Gateway传递的原始body字符串
        raw_payload = event['body']
        # 直接编码原始字符串生成签名
        expectedHash = hmac.new(key, msg=raw_payload.encode('utf-8'), digestmod=hashlib.sha256).hexdigest()
        
        if signatureHash != expectedHash:
            print(f"Signature mismatch: expected {expectedHash}, got {signatureHash}")
            return response
        else:
            response["status"] = 200
            response["body"] = expectedHash
            return response
    except Exception as e:
        print(f"Error: {str(e)}")
        return str(e)

2. 确保API Gateway传递完全原始的请求体

确认API Gateway的内容映射配置中,body字段直接使用$input.body,没有经过任何转义或修改:

#set($allParams = $input.params())
{
    "method": "$context.httpMethod",
    "params" : {
        #foreach($type in $allParams.keySet())
        #set($params = $allParams.get($type))
        "$type" : {
              #foreach($paramName in $params.keySet())
              "$paramName" : "$util.escapeJavaScript($params.get($paramName))"
              #if($foreach.hasNext),#end
              #end
        }
        #if($foreach.hasNext),#end
        #end
    },
    
    "body" : $input.body
    
}

该配置中body直接引用原始请求体,未做修改,是正确的。

3. 强制JSON序列化与Facebook格式一致(若需解析payload)

如果业务需要先解析payload再处理,序列化时必须严格匹配Facebook的格式:

  • 保留Unicode原始字符(ensure_ascii=False)
  • 按键名排序(sort_keys=True)
  • 去掉所有多余空格(separators=(',', ':'))

示例代码:

import json

# 解析原始body为字典
payload_dict = json.loads(event['body'])
# 严格按照Facebook的格式序列化
json_string = json.dumps(
    payload_dict,
    ensure_ascii=False,
    sort_keys=True,
    separators=(',', ':')
)
expectedHash = hmac.new(key, msg=json_string.encode('utf-8'), digestmod=hashlib.sha256).hexdigest()

验证步骤

  1. 在Lambda中开启详细日志,打印event['body']的原始内容和生成的expectedHash。
  2. 使用Facebook Webhook测试工具发送messages事件,复制工具中显示的原始payload。
  3. 用在线HMAC SHA256工具,输入App Secret和原始payload,计算签名,对比Lambda生成的expectedHash:
    • 若手动计算的签名与Facebook的signatureHash一致,说明Lambda处理payload的步骤有误。
    • 若手动计算的也不一致,检查App Secret是否正确,或原始payload是否被篡改。

注意事项

  • 签名对payload的任何细节(空格、换行、转义符、Unicode编码)都极度敏感,绝对不能修改原始请求体。
  • 确认App Secret是Facebook应用的正确密钥,无多余空格或换行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 23:55:18