目录
文章目录
  1. 1. 两条消息指向两个相反的方向
  2. 2. 服务器怎么判定一个时间戳能用
  3. 3. 本机比服务器快还是慢?拿 /api/v3/time 量
  4. 4. 排查顺序:调大 recvWindow 排在最后
  5. 5. 让 AI 写签名代码时,盯住 timestamp 在哪一行生成

币安 API 报 -1021:快 1 秒或超出 recvWindow 的时间戳会被拒

文字卡片:币安 API 报 -1021,本机时间快 1 秒以上或慢过 recvWindow 都会被拒,服务器默认窗口 5000 毫秒

-1021 是币安现货 API 的时间戳错误:请求带的 timestamp 要么比服务器时间快了 1 秒以上,要么旧到超出了 recvWindow,常见原因是本机时钟没对齐,或者脚本生成 timestamp 的位置不对。

2026-09-23 发布 PromptDeck 撰 阅读约 8 分钟 约 2,400 字
范围:币安现货 REST 接口(/api/v3)的 -1021 错误。规则与伪代码出自币安官方现货 API 文档仓库的 errors.md 和 rest-api.md(rest-api.md 最近一次提交为 2026-06-01);合约接口不在内。

「Timestamp for this request was 1000ms ahead of the server's time.」和「Timestamp for this request is outside of the recvWindow.」,这两条英文消息在币安官方文档里同属一个错误码:-1021 INVALID_TIMESTAMP。看到其中任何一条,说明服务器不接受这次请求带的时间戳,请求在转给撮合引擎之前就被拒了。

1. 两条消息指向两个相反的方向 #

币安官方现货 API 文档仓库 binance/binance-spot-api-docs 的 errors.md 里,-1021 下面列了两条消息,原文照录:

-1021 INVALID_TIMESTAMP
Timestamp for this request is outside of the recvWindow.
Timestamp for this request was 1000ms ahead of the server's time.

把消息字面和同一仓库 rest-api.md 里的服务器判断逻辑对照着读,两条各对应一个方向。这个对应关系是按字面推出来的,errors.md 本身没有逐条注明触发条件。

分清是哪一条,排查方向就定了一半:前一条要找谁把时间报快了,后一条要找时间差是在哪一段被拉长的。如果报的是 -1022 INVALID_SIGNATURE(原文「Signature for this request is not valid.」),那是签名校验没过,和时间戳是两个问题。

2. 服务器怎么判定一个时间戳能用 #

rest-api.md 的「Timing security」一节规定:SIGNED 请求除了签名,还必须带 timestamp 参数,值是当前时间戳,单位可以是毫秒或微秒。另有一个可选参数 recvWindow,指定请求在多长时间内有效,只接受毫秒,最多带三位小数来表示到微秒,文档举的例子是 6000.346。不传 recvWindow 时默认 5000 毫秒,最大 60000 毫秒。

币安官方现货 API 文档 rest-api.md 的 Timing security 一节截图:SIGNED 请求需带 timestamp 参数,recvWindow 默认 5000 毫秒、最大 60000 毫秒,服务器按伪代码判断 timestamp 是否超前 1 秒或超出 recvWindow,并建议 recvWindow 用 5000 或更小
图 1:「Timing security」一节,含 timestamp 与 recvWindow 的说明、服务器处理逻辑伪代码和 recvWindow 取值建议。出处 币安官方现货 API 文档 GitHub 仓库 rest-api.md,2026 年 9 月截取

图里那段伪代码,拆成中文是前后两道关:

  1. 收到请求时取一次服务器时间 serverTime。只有「timestamp 小于 serverTime 加 1000 毫秒」和「serverTime 减 timestamp 不超过 recvWindow」同时成立,服务器才开始处理。
  2. 开始处理之后再取一次 serverTime。这时 serverTime 减 timestamp 仍不超过 recvWindow,请求才转给撮合引擎;否则拒绝。

第一个条件管「太超前」,容差固定是 1 秒,和 recvWindow 设多少无关。第二个条件管「太旧」,上限就是 recvWindow。按公式,这个窗口从 timestamp 那一刻开始计时,而不是从请求到达服务器时开始,网络上耽搁的时间和服务器处理前后的时间都从同一个窗口里扣。

假设 recvWindow 用默认的 5000 毫秒,下面四种情形的数值是自拟的,单位都是毫秒。到达时的 serverTime 减 timestamp,等于单程网络耗时减去本机快出的量;本机慢的话,就是单程耗时加上慢的量。

情形本机时钟单程耗时到达时 serverTime − timestamp结果
A快 1,200100100 − 1,200 = −1,100超前超过 1,000,报 ahead
B快 1,200300300 − 1,200 = −900两个条件都满足,通过
C慢 4,500600600 + 4,500 = 5,100超过 5,000,报 outside recvWindow
D慢 4,500300300 + 4,500 = 4,800第一道关通过,只剩 200 的余量

A 和 B 的时钟快得一样多,结果却相反,因为 B 的网络耗时抵掉了一部分超前量。照伪代码推下去,一台时钟快 1 秒出头的机器,会随网络快慢时好时坏地报 ahead,看上去像偶发故障。D 虽然过了第一道关,服务器开始处理后还要再取一次时间,余下的 200 毫秒一旦用完,请求照样在第二道关被拒。

3. 本机比服务器快还是慢?拿 /api/v3/time 量 #

rest-api.md 的「Check server time」对应 GET /api/v3/time:用来测试连通性,返回当前服务器时间,不带任何参数,权重为 1。

量法:发请求前记下本机时间 t0,收到响应后记下本机时间 t1,把 (t0 + t1) / 2 当作服务器生成 serverTime 那一刻的本机时间。偏差 = serverTime − (t0 + t1) / 2,往返时间 = t1 − t0。偏差为正,说明服务器比本机快,也就是本机慢;为负则本机快。取中点的前提是去程和回程耗时差不多,线路两头不对称时,量出来的偏差会带一点误差。

# 量本机与币安服务器的时钟偏差(示意)
import time
import requests

def local_ms():
    return time.time() * 1000

for i in range(3):
    t0 = local_ms()
    server = requests.get("https://api.binance.com/api/v3/time").json()["serverTime"]
    t1 = local_ms()
    offset = server - (t0 + t1) / 2
    print(f"偏差 {offset:+.0f} ms,往返 {t1 - t0:.0f} ms")

下面是 2026 年 9 月 23 日的一组演示数据:一台经代理联网的机器,连续 3 次请求币安公开行情镜像 data-api.binance.vision 上的 /api/v3/time,按上面的量法得出。

项目三次结果(毫秒)三次平均
偏差 serverTime − (t0 + t1) / 2+792、+800、+812(792 + 800 + 812) / 3 ≈ +801
往返 t1 − t01,425、1,438、1,460(1,425 + 1,438 + 1,460) / 3 ≈ 1,441

偏差为正,这台机器的时钟比服务器慢约 0.8 秒。往返 1.4 秒偏大,是中间那层代理造成的。

用这组数估算它发签名请求时的情形:timestamp 比服务器时间早约 800 毫秒,单程耗时按往返的一半取约 720 毫秒(1,441 / 2),到达时 serverTime − timestamp 约为 800 + 720 = 1,520 毫秒,在默认的 5000 毫秒以内,不会触发 -1021。这台机器要报错,得是时钟慢的量加上单程耗时超过 recvWindow,报 outside recvWindow;或者反过来,时钟比服务器快 1 秒以上、网络耗时又没把这部分抵掉,报 ahead。你的机器请用上面的脚本另量。

4. 排查顺序:调大 recvWindow 排在最后 #

我们建议按这个顺序排查,前一步没排除,别急着跳到后一步。

  1. 认消息。报的是「1000ms ahead」,直接查本机时钟是不是偏快;这一条调 recvWindow 没用,那 1 秒的容差是固定的。报的是「outside of the recvWindow」,继续往下。
  2. 量偏差。用上一节的脚本连量几次。偏差接近 1 秒或更多,当成时钟问题处理;偏差很小、往返却很长,问题更可能在网络这一段。
  3. 打开操作系统的自动同步时间。同步完再量一次,确认偏差降下来了。脚本如果跑在云服务器或另一台电脑上,要量、要同步的是那台机器的时间。
  4. 查代码里 timestamp 在哪一步生成。每次签名前现取当前时间;重试时重新生成 timestamp 和签名,不复用第一次拼好的查询串。下一节有对照写法。
  5. 前四步都做了,网络确实慢,再把 recvWindow 适当调大。官方在 Timing security 一节加粗写着:「It is recommended to use a small recvWindow of 5000 or less! The max cannot go beyond 60,000!」

把调 recvWindow 放到最后,是因为这个窗口本身就是一道保护。同一节里有一句「Serious trading is about timing.」,后面解释:网络不稳定,请求到达服务器的时间有早有晚,recvWindow 用来规定请求必须在多少毫秒内被处理,超过就拒绝。窗口拉到 60000,意味着一个一分钟前签出的下单请求照样可以进撮合,那时的行情可能已经和你生成请求时看到的不一样了。

5. 让 AI 写签名代码时,盯住 timestamp 在哪一行生成 #

让 ChatGPT 或 Claude 写调用币安 API 的脚本时,timestamp 那一行很容易被一眼带过。下面两种是常见的写法错误示例,AI 生成的代码和手写代码里都可能出现。代码是伪代码,只演示 timestamp 在哪一步生成,不含真实 Key,也不是能直接下单的完整程序。

错误写法一,程序启动时生成一次 timestamp,之后每个请求都复用。假设本机时钟准、网络耗时忽略不计,启动 5 秒以后,serverTime − timestamp 就超过了默认的 5000;把 recvWindow 开到最大 60000,也只能撑到第 60 秒。脚本刚启动时那几个请求正常,跑一阵之后全部报 outside recvWindow,碰到这种现象,先查是不是这一种。

错误写法二,第一次请求拼好、签好之后,重试时把同一个查询串原样再发。第一次如果就是因为慢而报了 -1021,重发的 timestamp 只会更旧,重试几次结果都一样。只换 timestamp 不重算签名也不行,timestamp 是参与签名的参数之一,换了它,旧签名就和新参数对不上了。

# now_ms()、sign()、sign_all()、send()、is_error() 都是占位函数;Key 与 secret 从环境变量读取

# 错误写法一:启动时生成一次,之后一直复用
TS = now_ms()
def signed_request(path, params):
    params["timestamp"] = TS               # 永远是启动那一刻
    params["signature"] = sign(params)
    return send(path, params)

# 错误写法二:签好一次,重试时原样重发
query = sign_all({**params, "timestamp": now_ms()})
for attempt in range(3):
    resp = send(path, query)               # 每次都带着第一次的 timestamp
    if not is_error(resp, -1021):
        break

# 对照写法:每次发出前现取时间、现签名,重试从头走
def signed_request(path, params):
    p = dict(params)
    p["timestamp"] = now_ms()              # 紧挨着签名生成
    p["recvWindow"] = RECV_WINDOW           # 配置项,默认 5000
    p["signature"] = sign(p)               # timestamp 参与签名,换了就重签
    return send(path, p)

for attempt in range(3):
    resp = signed_request(path, params)
    if not is_error(resp, -1021):
        break

对照写法里,重试调用的是整个 signed_request,每一轮都重新取时间、重新签名。时钟本身偏了的话,这样重试几次也都会失败,要回到上一节的第 2、3 步。

让 AI 写这部分代码时,可以把下面几条贴在需求前面:

写调用币安现货 API 的签名请求时,按下面三条来:
1. timestamp 在签名函数内部、紧挨着签名那一行生成,不要在程序启动时生成后复用。
2. 任何重试都重新生成 timestamp、重新签名,不要重发之前拼好的查询串。
3. recvWindow 写成配置项,默认 5000;遇到 -1021 时把完整报错消息打印出来,不要自动调大 recvWindow。
示例里的 API Key 和 secret 一律用占位符,从环境变量读取。

代码交回来后,在里面找到 timestamp 被赋值的那一行,看它是不是在签名函数里、会不会每次请求都执行。改好的脚本先放到现货测试网上跑,接法见AI 写的币安交易脚本先跑现货测试网;实盘用的 Key 权限照给 AI 开 Binance API:只开只读权限,提现权限绝不能开来开;哪些决定不该交给 AI 生成的循环去做,参考千万不要让 AI 做的 7 件事

— PromptDeck, 2026-09-23

安全与合规提示:本页整理的是币安官方现货 API 文档(GitHub 仓库 binance/binance-spot-api-docs)中的时间戳规则,接口规则可能已有更新。不构成投资建议;在实盘运行自动交易脚本可能导致资金损失,运行前请确认你所在地区能否使用。 本站与 Binance 有推荐合作,仅在部分评测页使用推介链接;这篇排错文里的外链只有币安官方文档仓库。 完整披露 →