You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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使用同一套密钥。

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" }。

内容的提问来源于stack exchange,提问作者전태진

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 18:24:53