在Stripe Webhooks中传递Telegram ChatID作为客户端参考ID遇400错误
解决Stripe Webhook无法传递Telegram ChatID(client_reference_id)及400错误问题
问题核心
- 没法把Telegram ChatID作为
client_reference_id通过Stripe Webhooks传过来,导致同步Stripe数据时拿不到ChatID存入数据库 - 当前Webhook接口返回400 Bad Request错误
问题排查和解决办法
1. 先搞定400错误
Webhook返回400基本就两个原因:
- Payload解析错误:要么Stripe发送的payload格式异常,要么代码读取时修改了原始数据
- 签名验证失败:
endpoint_secret和Stripe后台配置的不一致,或者payload被中间件篡改
先核对这几点:
- 确认Stripe Dashboard里的Webhook密钥(
endpoint_secret)和代码里的完全一致,别多空格少字符 - 代码里
request.get_data(as_text=True)要确保没修改原始payload,Stripe签名验证必须用原封不动的payload - 检查服务器有没有自动解析JSON的中间件,这类中间件会破坏原始payload,导致验证失败
2. client_reference_id传不过来的关键原因
你Webhook代码里直接从session['client_reference_id']取值,但问题大概率出在创建Stripe Checkout Session的时候根本没设置这个字段——你提供的代码里没有创建Session的逻辑,这才是核心遗漏点。
正确设置client_reference_id的示例(加到你的Telegram机器人逻辑里)
用户触发订阅时,生成Checkout Session必须把client_reference_id设为用户的ChatID,注意要转成字符串(Stripe要求字符串类型):
# 示例:机器人处理订阅请求的逻辑 stripe.api_key = stripe_api_key def create_subscription_session(chat_id): try: session = stripe.checkout.Session.create( payment_method_types=['card'], line_items=[{ 'price': 'price_xxxxxx', # 替换成你自己的Stripe价格ID 'quantity': 1, }], mode='subscription', success_url='https://你的域名.com/success', cancel_url='https://你的域名.com/cancel', client_reference_id=str(chat_id) # 必须加这一行,把ChatID转成字符串 ) return session.url except stripe.error.StripeError as e: logging.error(f"创建Stripe会话出错:{e}") return None
3. Webhook代码的优化点
处理client_reference_id为空的情况
要是创建Session时没设这个字段,Webhook里直接取值会报错,加个判断避免崩溃:
if event['type'] == 'checkout.session.completed': session = event['data']['object'] chat_id = session.get('client_reference_id') # 用get方法,避免KeyError if not chat_id: logging.error("Checkout Session里没找到client_reference_id") return jsonify(success=True), 200 # 别返回400,不然Stripe会重复发事件 amount_total = session['amount_total'] # 后面的逻辑不变...
修复数据库更新的问题
如果user_subscription表里没有这个ChatID的记录,UPDATE语句不会生效,改成INSERT ... ON CONFLICT既能新增又能更新:
cursor.execute( """ INSERT INTO user_subscription (chat_id, subscription_plan) VALUES (%s, %s) ON CONFLICT (chat_id) DO UPDATE SET subscription_plan = EXCLUDED.subscription_plan """, (chat_id, subscription_plan) )
4. 测试步骤
- 用Stripe CLI测试Webhook:
然后触发一个测试的stripe listen --forward-to localhost:4242/webhookcheckout.session.completed事件,看日志能不能拿到client_reference_id - 去Stripe Dashboard的Checkout Sessions列表里,查看创建的Session详情,确认
client_reference_id确实存在
修正后的Webhook代码片段
# 处理Stripe Webhook事件 @app.route('/webhook', methods=['POST']) def webhook(): payload = request.get_data(as_text=True) sig_header = request.headers.get('Stripe-Signature') try: event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret) except ValueError as e: logging.error(f"无效payload:{e}") return jsonify(success=False), 400 except stripe.error.SignatureVerificationError as e: logging.error(f"签名验证失败:{e}") return jsonify(success=False), 400 # 处理会话完成事件 if event['type'] == 'checkout.session.completed': session = event['data']['object'] chat_id = session.get('client_reference_id') if not chat_id: logging.error("Checkout Session中未找到client_reference_id") return jsonify(success=True), 200 amount_total = session['amount_total'] # 根据金额判断订阅计划 subscription_plan = 'Weekly' if amount_total == 695 else 'Monthly' if amount_total == 1995 else 'Unknown' # 更新数据库 conn = get_db_connection() if conn is not None: cursor = conn.cursor() try: cursor.execute( """ INSERT INTO user_subscription (chat_id, subscription_plan) VALUES (%s, %s) ON CONFLICT (chat_id) DO UPDATE SET subscription_plan = EXCLUDED.subscription_plan """, (chat_id, subscription_plan) ) conn.commit() logging.info(f"已更新chat_id {chat_id}的订阅计划") except Exception as e: logging.error(f"数据库错误:{e}") finally: cursor.close() conn.close() else: logging.error("数据库连接失败") return jsonify(success=True), 200
内容的提问来源于stack exchange,提问作者Rafay Imran
相关产品推荐
相关产品推荐

