Flask中无法验证Meta/WhatsApp Webhook的X-Hub-Signature-256,求排查或示例
Flask中Meta/WhatsApp Webhook的X-Hub-Signature-256签名验证问题
我在Flask框架中无法成功验证Meta/WhatsApp Webhook的X-Hub-Signature-256签名,希望有人帮忙排查代码错误,或者提供可正常运行的示例。我的代码实现如下:
import base64 import hashlib import hmac import os from dotenv import load_dotenv from flask import Flask, jsonify, request from werkzeug.middleware.proxy_fix import ProxyFix load_dotenv() API_SECRET = os.environ.get('API_SECRET') app = Flask(__name__) app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_host=1) def verify_webhook(data, hmac_header): hmac_recieved = str(hmac_header).removeprefix('sha256=') digest = hmac.new(API_SECRET.encode('utf-8'), data, digestmod=hashlib.sha256).digest() computed_hmac = base64.b64encode(digest) return hmac.compare_digest(computed_hmac, hmac_recieved.encode('utf-8')) @app.route("/whatsapp", methods=["GET", "POST"]) def whatsapp_webhook(): if request.method == "POST": try: data = request.get_data() if not verify_webhook(data, request.headers.get('X-Hub-Signature-256')): return "", 401 except Exception as e: print(e) return "", 500 return jsonify({"status": "success"}, 200)
问题排查与修复方案
密钥混淆问题
很多人会把Webhook的Verify Token和Meta应用的App Secret搞混——X-Hub-Signature-256签名验证必须使用App Secret(可在Meta开发者后台的应用设置中找到),而非Webhook配置页的Verify Token。先确认你的API_SECRET环境变量对应的是正确的App Secret。空Header处理漏洞
若请求未携带X-Hub-Signature-256头,你的代码会把None转成字符串"None"再执行removeprefix,直接导致验证逻辑失效。需先判断Header是否存在且格式正确,不存在直接返回验证失败。编码匹配细节优化
原代码中computed_hmac是base64编码后的bytes对象,与hmac_recieved.encode('utf-8')的bytes比较逻辑可行,但转成字符串后再比较可读性更强,也能避免潜在编码问题。
修复后的可运行代码
import base64 import hashlib import hmac import os from dotenv import load_dotenv from flask import Flask, jsonify, request from werkzeug.middleware.proxy_fix import ProxyFix load_dotenv() # 提前校验API_SECRET是否存在 API_SECRET = os.environ.get('API_SECRET') if not API_SECRET: raise ValueError("API_SECRET环境变量未设置或为空") # 单独配置Webhook验证用的Verify Token VERIFY_TOKEN = os.environ.get('VERIFY_TOKEN') if not VERIFY_TOKEN: raise ValueError("VERIFY_TOKEN环境变量未设置或为空") app = Flask(__name__) app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_host=1) def verify_webhook(data, hmac_header): # 先校验Header的存在性与格式 if not hmac_header or not hmac_header.startswith('sha256='): return False hmac_received = hmac_header.removeprefix('sha256=') # 生成计算签名 digest = hmac.new( API_SECRET.encode('utf-8'), data, digestmod=hashlib.sha256 ).digest() computed_hmac = base64.b64encode(digest).decode('utf-8') # 使用安全比较方法避免时序攻击 return hmac.compare_digest(computed_hmac, hmac_received) @app.route("/whatsapp", methods=["GET", "POST"]) def whatsapp_webhook(): if request.method == "GET": # 处理Meta的Webhook订阅验证请求 mode = request.args.get('hub.mode') token = request.args.get('hub.verify_token') challenge = request.args.get('hub.challenge') if mode and token and mode == 'subscribe' and token == VERIFY_TOKEN: return challenge, 200 return "", 403 elif request.method == "POST": try: # 必须获取原始请求体,不能提前解析JSON(会修改请求体导致签名不匹配) data = request.get_data() hmac_header = request.headers.get('X-Hub-Signature-256') if not verify_webhook(data, hmac_header): return "", 401 # 此处添加Webhook事件处理逻辑,例如: # event = request.get_json() # print("收到事件:", event) except Exception as e: print(f"验证或处理出错: {e}") return "", 500 return jsonify({"status": "success"}), 200
额外注意事项
- 验证签名前禁止调用
request.get_json()或其他解析请求体的方法,否则请求体格式会被修改,导致签名不匹配。 - 若应用部署在反向代理(如Nginx)后,需确保代理未修改请求体或请求头,
ProxyFix中间件已处理X-Forwarded-*头,但仍需确认代理配置正确。 - 测试时可通过curl模拟携带正确签名头的请求,验证逻辑是否正常。
内容的提问来源于stack exchange,提问作者Gurkenkönig
相关产品推荐
相关产品推荐

