文章/2026 年 Bybit V5 API 鉴权实操:HMAC 签名、权限分级与常见错误码排查
工具评测

2026 年 Bybit V5 API 鉴权实操:HMAC 签名、权限分级与常见错误码排查

Bybit V5 文档把签名算法写清楚了,但没告诉你新手第一周一定卡在 10004 上。这篇拆开 V5 鉴权全流程:HMAC 签名 5 步走、API key 权限矩阵、错误码地图、recv_window 时间漂移。

2026年5月25日阅读时间: 11 分钟0 个主题标签
阅读过渡

上面是文章摘要,下面进入正文深读。可以配合目录逐段阅读,不会丢掉上下文。

工具评测7 个章节

上一个 sprint 我给一个 basis trade 策略接 funding rate 监控,篮子里只有 Bybit V5 让我前六个请求全吃了 retCode: 10004。API key 没错,secret 没错,端点是文档里指明的那一个,响应还是说签名不对。Bybit V5 官方文档把签名规范写清楚了——HMAC 进去什么字节、用哪些 header——但是没告诉你新手第一周一定要花掉的那半天,全在排错误码上面。这篇就是我当时希望存在的那篇连接文。

如果你是从 "bybit api docs" 或者 "bybit api documentation" 之类的搜索结果跳进来的,你要找的大概率是这一层,而不是又一份 Bybit API 端点清单。下面先用 Python 把 V5 签名一步步搭出来,然后讲清楚每个 API key 权限到底解锁了什么,最后给一份你可以一直开着的错误码地图。

V5 改了什么,你的 V3 代码为什么悄悄坏了

Bybit 在 2023 年初上线了 V5 统一接口,之后一年陆续把 Spot、Linear、Inverse、Option 的 V3 surface 都标了废弃。到 2026 年,V3 对新账户已经基本下线,以前能跑的搬运代码会在两个地方安静地坏掉。

第一是 prehash 的拼法。V3 是把 query 参数按字母排序后拼成签名串。V5 不排序。prehash 的形态是:

timestamp + api_key + recv_window + (queryString | rawBody)

GET 请求第四段是 URL 里参数实际出现的那个顺序的 query string。POST 请求第四段是 JSON body 的原始字节,发出去什么就签什么。如果 HTTP 客户端在你签完之后又重排了 JSON key,服务端看到的 body 和你签的就不一样了,签名直接挂。

第二是 header 集。V3 允许你把 api_keytimestampsign 这些字段塞进 URL 或者 body。V5 把所有鉴权元数据全部挪到 header——X-BAPI-API-KEYX-BAPI-TIMESTAMPX-BAPI-RECV-WINDOWX-BAPI-SIGN,再加一个 X-BAPI-SIGN-TYPE: 2 表示 HMAC SHA256。如果旧 wrapper 里还有把 api_key 塞进 query string 的逻辑,V5 端点要么忽略要么直接拒绝,看具体接口。

字段 V3 V5
prehash 拼接 参数按字母排序 timestamp + api_key + recv_window + payload,按这个顺序
POST body 签名 排序后的 JSON 原始 JSON body,字节级一致
鉴权位置 URL/body 只走 header
签名 header sign query 或 body 字段 X-BAPI-SIGN
签名类型 隐式 X-BAPI-SIGN-TYPE: 2(HMAC)或 1(RSA)

迁移最干净的做法是把 V3 签名 helper 整个扔掉,V5 从头写。二十行 Python 比改一千行围绕字母排序设计的老 wrapper 省事得多。

五步搭出 V5 签名

下面是完整可跑的签名函数,覆盖 GET 和 POST。不用 SDK,只要 requestshmac

import hmac
import hashlib
import json
import time
import requests

API_KEY = "YOUR_KEY"
API_SECRET = "YOUR_SECRET"
BASE_URL = "https://api.bybit.com"
RECV_WINDOW = "5000"

def sign(payload: str, timestamp: str) -> str:
    prehash = timestamp + API_KEY + RECV_WINDOW + payload
    return hmac.new(
        API_SECRET.encode("utf-8"),
        prehash.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()

def signed_get(path: str, params: dict) -> dict:
    # URL 里的参数顺序必须和 prehash 里一致
    query_string = "&".join(f"{k}={v}" for k, v in params.items())
    timestamp = str(int(time.time() * 1000))
    signature = sign(query_string, timestamp)
    headers = {
        "X-BAPI-API-KEY": API_KEY,
        "X-BAPI-TIMESTAMP": timestamp,
        "X-BAPI-RECV-WINDOW": RECV_WINDOW,
        "X-BAPI-SIGN": signature,
        "X-BAPI-SIGN-TYPE": "2",
    }
    url = f"{BASE_URL}{path}?{query_string}"
    return requests.get(url, headers=headers).json()

def signed_post(path: str, body: dict) -> dict:
    # 关键:只序列化一次,签那一份字节,发那一份字节
    body_str = json.dumps(body, separators=(",", ":"))
    timestamp = str(int(time.time() * 1000))
    signature = sign(body_str, timestamp)
    headers = {
        "X-BAPI-API-KEY": API_KEY,
        "X-BAPI-TIMESTAMP": timestamp,
        "X-BAPI-RECV-WINDOW": RECV_WINDOW,
        "X-BAPI-SIGN": signature,
        "X-BAPI-SIGN-TYPE": "2",
        "Content-Type": "application/json",
    }
    return requests.post(f"{BASE_URL}{path}", headers=headers, data=body_str).json()

这段代码按顺序做了五件事:

  1. 拿毫秒时间戳。 Bybit 要的是 Unix epoch 毫秒,不是秒。int(time.time() * 1000) 是对的,int(time.time()) 一发就 10002。
  2. 拼 payload。 GET 用 URL 的 query string;POST 用紧凑形态的 JSON body 字符串。注意 separators=(",", ":")——json.dumps 默认会在分隔符后面塞空格,那些空格是要被签进去的字节。
  3. 拼 prehash。 timestamp + api_key + recv_window + payload,段与段之间不加任何分隔符,也不要按字母排序。
  4. HMAC-SHA256 用 secret 加密。 输出 hex digest,不是 base64,V5 规范明确写了 hex。
  5. 发出去的 body 字节要和签的一致。 POST 这里务必用 data=body_str 而不是 json=body,后者会重新序列化,发出去的字节和你签的不是一份了。

第一次贴上去跑不通,十次有八次卡在第 5 步的 POST 坑。requests.post(..., json=body) 会美化 JSON 加空格,签的字节和发的字节就对不上了。永远签上面那份 body_str,再把同一份 body_str 放上线。

API key 权限分级在实战中怎么勾

Bybit API Management 页面给的是一棵勾选树,但分组和端点不是简单一一映射。下面这张表是每个权限实际解锁的能力,以及实战机器人通常勾哪几个组合。

权限组 解锁内容 典型机器人
Read-Only 所有 GET 端点:行情、账户状态、仓位、订单、成交 只读 dashboard、资金费率监控
Unified Trading → Spot 现货下单/撤单/改单,现货杠杆 现货网格、DCA
Unified Trading → Derivatives Linear 和 Inverse 永续,杠杆和保证金模式 永续做市、basis trade
Unified Trading → Options 期权下单、多腿组合 期权卖方/波动率策略
Asset Transfer 钱包内转账、主子账号转账、小额币种 convert 资金调度、子账号路由
Earn 申购/赎回活期和定期 Earn 产品 闲置资金扫地僧
Affiliate Affiliate 专属上报端点 邀请佣金 dashboard
Copy Trading 带单和跟单管理 跟单调度
Withdraw 提现到白名单地址 自动提现脚本(小心用)

UI 上看不出来的两个实战点:

第一,Read-Only 不是交易权限的超集,在那些需要读+写上下文的端点上要单独勾。 Derivatives 的下单端点会隐式读你的仓位做保证金校验,但你不能用一个 Read-Only key 也去下单——这俩是独立授权。机器人如果既读仓位又下单,Read-Only 和对应交易权限都要勾上。

第二,Withdraw 是唯一 Bybit 强制绑 IP 白名单的权限,而且提现 key 只能往账户设置里已经加白的地址打钱。如果创建 key 的时候忘了填 IP,这个开关是灰的。IP 要先想好再建 key,否则要重建。

你真正需要的错误码地图

V5 错误码总量上千,加上端点特化的(合约找不到、仓位超限之类)能更多。鉴权层这一面,新手第一周会遇到的主要在 100xx 段。下面这张是实战 triage 表:

错误码 含义 第一步排查
10001 参数错误 必填字段漏了或者类型错了。把发出去的 params 打印出来,对照端点 schema
10002 请求时间戳无效,请检查 timestamp / recv_window 服务端时间减你的时间戳超过 recv_window。NTP 对时,或者把 X-BAPI-RECV-WINDOW 调到 10000+
10003 API key 无效 key 字符串错了,或者在 UI 上被删了,或者你用 testnet key 打了 mainnet(反之亦然)
10004 签名错误 签名对不上。回上面 V5 prehash 那节,POST body 那个坑是最常踩的
10005 当前 API key 权限不足 端点要的权限你 key 没勾上。重看权限矩阵,必要时重建 key
10006 访问频率超限 限流触发了。退避一下,看看这个数据是不是该用 WebSocket 拉
10010 IP 不匹配 请求来源 IP 不在 key 的白名单里。机器人换机房之后特别容易撞这个
10016 服务端错误 Bybit 那边的问题,指数退避重试就好,别先怀疑自己
10018 IP 限流超限 和 10006 不一样,这个是每 IP 的限额。分 IP 或者降速

任何 100xx 触发,建议的排查顺序:

  1. 同样的 payload 字节,本机用 curl 重打一遍。本机能过、VPS 过不去,问题在 10010 或者跨区时间漂移。
  2. 在代码里把 prehash 字符串和签名打出来。retCode 是 10004 但 prehash 看上去对,那是 POST body 在线上被改了字节。
  3. 在 bot 主机上请求 GET /v5/market/time(这个端点无需鉴权),结果减本地时钟。漂移超过 1 秒,行情活跃那几分钟 10002 一定会来。

recv_window 和跨区时间漂移

recv_window 的判定是地理分布式机器人最容易翻车的地方。默认 5000 毫秒看着宽,直到你的 VPS 在圣保罗、Bybit 接入点解析到东京节点,美盘 funding flip 那几分钟 RTT 偶尔顶到 4 秒——这时候请求就开始静默失败。

生产环境两种打法可行:

  • 宽窗口、低信任路径。 X-BAPI-RECV-WINDOW 拉到 15000-20000,接受略弱的 replay 防护。读端点或者非关键下单可以这么做。
  • NTP 严格对时、窄窗口。chronysystemd-timesyncd 接近源时间池,recv_window 保持 5000,接受偶尔网络抖动产生的 10002。

错误的打法是 SDK 默认 5000 完全不管。Bybit 直接拒,从用户视角看,机器人就在最关键的几分钟停止下单,外面看不出错。

如果想要一行偏执防御,加一个"分钟级"对时——每分钟调一次 GET /v5/market/time 维护一个 offset 变量,所有出站时间戳都减/加这个 offset。这是每个生产级 Bybit wrapper 都在做的事,分钟一次的限流成本几乎可以忽略。

Testnet、Mainnet、Demo Trading 是三套环境

值得单独说一下,因为每个团队都会在某个时刻搞混一次:

  • Mainnethttps://api.bybit.com。真钱真盘口。
  • Testnethttps://api-testnet.bybit.com。完全独立的用户数据库,独立 API key,有 faucet 发测试资金,盘口和真实市场不像。签名调试和端到端测试用它,策略验真别用。
  • Demo Tradinghttps://api-demo.bybit.com。从主网账户开出来,跑在真实市场数据上但是用虚拟余额。策略验真用它,能拿到真实深度、真实 funding 周期、真实波动率。

新手最常见的错是先在 testnet 注册熟悉、上线时忘了 testnet key 在 mainnet 完全不认。两个数据库不互通。上线前在 mainnet 新建一把 key 是标准动作。

接下来怎么用这篇

打开 Bybit API 目录页拿端点清单,把上面那段 Python 签名函数贴进项目,第一个请求打 GET /v5/account/wallet-balance?accountType=UNIFIED 看能不能拿到真实余额。能过的话,V5 后面所有端点的形态都一样——鉴权层就这一份,剩下的全是端点 schema。如果 wallet-balance 通了但其他端点还在 10004,下一个嫌疑犯永远是 POST body 序列化那个坑,从那查起。

分享文章

文章概览

读完前先看这几项

分类
工具评测
阅读时间
11 分钟
提到的工具
0
返回文章列表 →

下一步

读完后可以继续回到工具目录,对比具体产品。

去看工具