如何在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()
验证步骤
- 在Lambda中开启详细日志,打印
event['body']的原始内容和生成的expectedHash。 - 使用Facebook Webhook测试工具发送messages事件,复制工具中显示的原始payload。
- 用在线HMAC SHA256工具,输入App Secret和原始payload,计算签名,对比Lambda生成的
expectedHash:- 若手动计算的签名与Facebook的
signatureHash一致,说明Lambda处理payload的步骤有误。 - 若手动计算的也不一致,检查App Secret是否正确,或原始payload是否被篡改。
- 若手动计算的签名与Facebook的
注意事项
- 签名对payload的任何细节(空格、换行、转义符、Unicode编码)都极度敏感,绝对不能修改原始请求体。
- 确认App Secret是Facebook应用的正确密钥,无多余空格或换行。
内容的提问来源于stack exchange,提问作者Rishabh Tyagi
相关产品推荐
相关产品推荐

