PayPal沙箱Webhook签名验证:部分事件失败求助
PayPal Webhook验证失败排查方案
问题描述
使用Python+FastAPI开发PayPal集成,两个Webhook接口逻辑如下:
/capture_payment接收CHECKOUT.ORDER.APPROVED事件,验证通过后触发支付捕获;- 支付完成后,PayPal推送
PAYMENT.CAPTURE.COMPLETED事件至/register_payment接口,验证后执行后续操作。
接口代码:
@app.post("/capture_payment") # 接收CHECKOUT.ORDER.APPROVED事件 async def handle_webhook(request: Request): headers = request.headers event = await request.json() payment_processor = PayPalPayment() verification = payment_processor.verify_webhook_signature(headers, event)['verification_status'] if verification == 'SUCCESS': print('Validated!') order_id = event['resource']['id'] result = payment_processor.capture_order(order_id) # 触发后续PAYMENT.CAPTURE.COMPLETED事件 return {"code" : 200} else: return {"code": 400} @app.post("/register_payment") # 接收PAYMENT.CAPTURE.COMPLETED事件 async def handle_webhook(request: Request): headers = request.headers event = await request.json() payment_processor = PayPalPayment() verification = payment_processor.verify_webhook_signature(headers, event)['verification_status'] if verification == 'SUCCESS': print('Valid payment capture event received!') # 后续业务操作 return {'code' : 200} print('Webhook not verified!') return {'code' : 400}
签名验证函数:
def verify_webhook_signature(self, webhook_headers, event): auth_algo = webhook_headers['PAYPAL-AUTH-ALGO'] cert_url = webhook_headers['PAYPAL-CERT-URL'] transmission_id = webhook_headers['PAYPAL-TRANSMISSION-ID'] transmission_sig = webhook_headers['PAYPAL-TRANSMISSION-SIG'] transmission_time = webhook_headers['PAYPAL-TRANSMISSION-TIME'] webhook_id = get_webhook_id(event['event_type']) url = 'https://api-m.sandbox.paypal.com/v1/notifications/verify-webhook-signature' headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {get_bearer_token()}', } data = { 'auth_algo': auth_algo, 'cert_url': cert_url, 'transmission_id': transmission_id, 'transmission_sig': transmission_sig, 'transmission_time': transmission_time, 'webhook_id': webhook_id, 'webhook_event': event } response = requests.post(url, headers=headers, json=data) return response.json() def get_webhook_id(event_type : str): id = os.getenv(f'PAYPAL_WEBHOOK_ID_{event_type.upper()}') print(f'webhook id: {id}') return id
当前CHECKOUT.ORDER.APPROVED事件验证正常,但PAYMENT.CAPTURE.COMPLETED事件验证始终失败,Postman单独测试结果一致,需排查原因。
排查与解决方向
1. Webhook ID配置错误
- 检查环境变量
PAYPAL_WEBHOOK_ID_PAYMENT.CAPTURE.COMPLETED是否正确设置:注意事件类型中的.需完整保留,不能被替换或遗漏 - 确认该Webhook ID是PayPal后台为
PAYMENT.CAPTURE.COMPLETED事件配置的(可在沙箱商家账户的Webhook设置中查看),避免和CHECKOUT.ORDER.APPROVED的Webhook ID混用 - 查看控制台打印的
webhook id,确认其不为空或与PayPal后台配置一致
2. 沙箱Webhook订阅配置问题
- 登录沙箱商家账户,确认Webhook已订阅
PAYMENT.CAPTURE.COMPLETED事件,且回调地址指向/register_payment接口 - 沙箱与生产环境的Webhook ID完全独立,确保使用的是沙箱环境的Webhook ID,而非生产环境的
3. 请求数据完整性问题
- 检查FastAPI的
request.headers是否完整获取了所有PAYPAL-*开头的头部字段,避免被反向代理、中间件修改或过滤 - 确保
webhook_event是原封不动的PayPal推送内容,未在接收时修改JSON结构(如字段排序、添加/删除内容),PayPal签名验证依赖事件内容的绝对完整性
4. Bearer Token权限问题
- 确认生成Bearer Token的沙箱应用关联了正确的商家账户,且拥有
notifications:webhooks:verify权限 - 检查Token是否过期,或是否误用生产环境的Token验证沙箱事件
5. 时间同步问题
PayPal的签名验证会检查事件发送时间与服务器时间的差值(允许范围通常为5分钟),需:
- 确保服务器系统时间与UTC时间同步,避免因时间偏差导致验证失败
内容的提问来源于stack exchange,提问作者jojusuar
相关产品推荐
相关产品推荐

