Cloudflare Worker定时调用Bybit API遇403错误求助
问题:Cloudflare Worker定时调用Bybit API返回403错误,手动执行正常
通过Cloudflare Worker的定时执行功能,每2分钟调用一次Bybit API获取平仓盈亏数据。手动触发请求时能正常获取数据,但自动执行时却返回403错误,不清楚遗漏了什么配置或代码逻辑。
实现代码
export default { // ✅ 自动执行(每2分钟执行一次) async scheduled(event, env) { console.log("⏰ [自动执行] 每2分钟执行中..."); await fetchBybitData(env, "scheduled"); }, // ✅ 手动执行(在Cloudflare Workers中直接调用) async fetch(request, env) { console.log("🛠️ [手动执行] 收到请求!"); const result = await fetchBybitData(env, "fetch"); return new Response(JSON.stringify(result, null, 2), { status: 200, headers: { "Content-Type": "application/json" } }); } }; // 📌 获取Bybit数据的通用函数 async function fetchBybitData(env, source) { const API_KEY = ""; const API_SECRET = ""; const endpoint = "https://api.bybit.com/v5/position/closed-pnl"; const category = "linear"; const symbol = "BTCUSDT"; const recvWindow = "5000"; const now = Date.now(); const startTime = now - (3 * 24 * 60 * 60 * 1000); const endTime = now; const timestamp = now.toString(); const params = [ `api_key=${API_KEY}`, `category=${category}`, `endTime=${endTime}`, `recv_window=${recvWindow}`, `startTime=${startTime}`, `symbol=${symbol}`, `timestamp=${timestamp}` ].sort().join("&"); const signature = await generateSignature(API_SECRET, params); const requestUrl = `${endpoint}?${params}&sign=${signature}`; console.log(`🔎 [${source.toUpperCase()} 执行] Bybit API 请求 URL:`, requestUrl); try { const response = await fetch(requestUrl, { method: "GET", headers: { "Content-Type": "application/json" } }); // 📌 确认是否为JSON响应 const contentType = response.headers.get("content-type") || ""; if (!contentType.includes("application/json")) { console.error("🚨 API返回的不是JSON而是HTML!响应内容:", await response.text()); throw new Error("API返回HTML导致JSON解析失败!"); } const result = await response.json(); if (result.retCode === 0) { const processedData = (result.result.list || []).map(item => ({ symbol: item.symbol, leverage: item.leverage, updatedTime: convertToKST(item.updatedTime), side: item.side === "Buy" ? "Long" : "Short", closedPnl: item.closedPnl, qty: item.qty })); console.log(`✅ [${source.toUpperCase()} 执行] Bybit数据更新完成!`); await env.Bybit_PnL.put("Bybit_PnL", JSON.stringify(processedData)); return processedData; } else { console.warn(`🚨 请求失败: ${result.retMsg}`); return { error: result.retMsg }; } } catch (error) { console.error(`🚨 [${source.toUpperCase()} 执行] 请求中发生错误: ${error.message}`); return { error: error.message }; } } // 📌 生成HMAC SHA-256签名 async function generateSignature(secret, payload) { const encoder = new TextEncoder(); const key = await crypto.subtle.importKey( "raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"] ); const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload)); return [...new Uint8Array(signature)].map(b => b.toString(16).padStart(2, "0")).join(""); } // 📌 转换为韩国标准时间(KST)的函数 function convertToKST(timestamp) { const date = new Date(Number(timestamp)); if (isNaN(date.getTime())) return "Invalid Date"; return date.toLocaleString("ko-KR", { timeZone: "Asia/Seoul" }); }
排查方向及解决方案
1. 时间同步偏差(最可能原因)
Bybit API对timestamp的时效性要求严格,定时任务环境的系统时间可能和本地存在偏差,导致请求的timestamp超出recvWindow(当前设置为5000ms)的有效范围,触发403。
- 调整方案:
- 增大
recvWindow到10000或20000,给时间偏差留足余量; - 定时任务中改用
event.scheduledTime.getTime()作为timestamp的来源,该时间由Cloudflare调度系统提供,准确性更高; - 替换
Date.now()为new Date().getTime(),避免环境时间偏移问题。
- 增大
2. 密钥配置不一致
代码中API_KEY和API_SECRET为空,推测是通过环境变量注入。需确认定时任务环境的变量是否和手动执行的Worker环境一致,避免密钥不匹配。
- 调整方案:
- 将密钥存储到Cloudflare Worker的环境变量中,代码中改用
env.API_KEY和env.API_SECRET调用; - 检查定时任务的环境变量配置,确保和手动执行的Worker使用同一套密钥。
- 将密钥存储到Cloudflare Worker的环境变量中,代码中改用
3. 请求头冗余导致校验失败
GET请求无需携带Content-Type: application/json头,Bybit可能对多余请求头做严格校验,触发拦截。
- 调整方案:
- 删除
headers: { "Content-Type": "application/json" },改用空对象headers: {}; - 避免Cloudflare自动添加的冗余头,可在fetch配置中显式指定必要头。
- 删除
4. 签名生成逻辑差异
对比手动和定时执行时的params字符串,确认参数排序、拼接是否完全一致,避免签名生成时的payload差异。
- 调整方案:
- 在日志中打印手动/定时执行的
params和signature,对比两者是否一致; - 确认参数排序是按ASCII升序(当前代码的
sort()方法符合要求,但需确保无参数遗漏)。
- 在日志中打印手动/定时执行的
5. IP段被Bybit拦截
定时任务使用Cloudflare的服务器IP发起请求,可能被Bybit的反爬机制拦截,而手动执行用的是本地IP,不受限制。
- 调整方案:
- 在fetch配置中添加
cf: { resolveOverride: "api.bybit.com" }强制解析到Bybit的官方IP; - 尝试给请求添加
User-Agent头,模拟常规浏览器请求,比如headers: { "User-Agent": "Mozilla/5.0" }。
- 在fetch配置中添加
内容的提问来源于stack exchange,提问作者전태진
相关产品推荐
相关产品推荐

