Avtim

研究

Clock skew and signed requests: why a venue rejects your perfectly valid order

Every signed request carries a timestamp, and every venue has an opinion about how far from its own clock that timestamp may be. The error messages rarely say "your clock is off". Here is what they say instead, and how to make the problem go away for good.

研究文章以英文发布。

2026年10月26日 阅读约 4 分钟 #connectivity#clocks#errors

A connector that has worked for weeks starts getting rejections. Nothing in the code changed. The signature is computed the same way, the key is valid, the request body is identical to the one that worked yesterday. The venue says the signature is wrong, or the request expired, or the timestamp is outside some window. The cause is almost always the same: the clock on the machine drifted, and the venue’s tolerance ran out.

What venues check

Signed requests on most venues include a client timestamp, in milliseconds since the Unix epoch, that is part of the signed payload. The venue compares it with its own clock and rejects the request if the difference is larger than a window. The window and the error you get differ by venue; the table summarises what their documentation said in October 2026. Venues change these details without ceremony, so treat it as a map, not as a contract.

Venue Tolerance What the rejection looks like
Binance recvWindow parameter, default 5000 ms, maximum 60000 ms; the request must also not arrive more than 1 s before the server’s time Error code -1021, “Timestamp for this request is outside of the recvWindow”
Bybit recv_window header, default 5000 ms Return code 10002, “invalid request, please check your server timestamp or recv_window param”
OKX Timestamp must be within 30 s of server time, ISO 8601 format Code 50102, “Timestamp request expired”
Bitget Timestamp must be within 30 s of server time Code 40008, “Request timestamp expired”
KuCoin Timestamp must be within 5 s of server time Code 400002, “KC-API-TIMESTAMP Invalid”
Gate Timestamp must be within 60 s of server time “REQUEST_EXPIRED”
MEXC recvWindow on spot, default 5000 ms; futures compares the request time header with server time Spot: code 700003, “Timestamp for this request is outside of the recvWindow”. Futures: a clock problem can surface as a signature error, which sends people chasing the wrong bug

Two things stand out. The tolerance ranges from five seconds to a minute, so a clock that is fine for one venue can be rejected by another. And on some venues the error names the signature rather than the clock, because the timestamp is part of what is signed and the venue’s check runs in an order that reports the signature mismatch first.

Why clocks drift

A server’s clock is disciplined by NTP, usually against a pool of public time servers. On a well-run machine the offset stays within a few milliseconds. It goes wrong when NTP is not running, when the machine is a virtual guest whose host pauses it, when a container image ships without a time service, or when a firewall blocks UDP port 123 and nobody notices because nothing else complains. A drift of a few seconds per week is enough to cross the KuCoin window in a month and the Binance default in a few weeks.

Cloud providers now offer their own time sources with much tighter accuracy than public NTP. Using them is the right default on their machines; it does not, however, remove the need for the next step.

Measure the offset to each venue, continuously

Fixing your clock is necessary but not sufficient, because the venue’s clock is not perfect either, and because what matters for the signature check is the difference between the two, not the absolute accuracy of yours. The robust approach is to treat the offset to each venue as a measured quantity:

  1. On connection, call the venue’s server-time endpoint (the same cheap call our latency probe uses). Record the request send time, the venue’s reported time, and the response receive time. The offset estimate is the venue time minus the midpoint of send and receive; the uncertainty is half the round trip.
  2. Keep a running estimate per venue, refreshed every few minutes, and feed it into the timestamp you put into signed requests. On a venue that enforces a five-second window, you now have the whole window as margin instead of hoping.
  3. Alarm on the offset, not on the rejection. If the measured offset moves by more than a second, something is wrong with a clock, yours or theirs, and you want to know before orders fail.

This is the approach we take in our own connectors: the clock offset to a venue is a data series, stored next to the latency series, not a configuration value.

Do not widen the window to make the error go away

Setting recvWindow to its maximum is the common quick fix. It works, in the sense that rejections stop. It also means that a request delayed by tens of seconds somewhere in the network will still be executed when it finally arrives, which is precisely what the window exists to prevent. Keep the window small, keep the clock honest, and measure the offset.

Checklist

  • Is a time service running, and is it actually syncing? On Linux, chronyc tracking or timedatectl will tell you.
  • Is UDP 123 allowed out, or are you using the cloud provider’s time source over a link-local address?
  • Does the connector measure the offset to each venue on connect and refresh it?
  • Does it alarm on offset changes?
  • Is recvWindow at the default, not at the maximum?

If all five are yes, clock-related rejections stop being a category of incident.


由 Avtim 工程团队撰写。文中引用的交易所文档数据在撰写时有效;交易场所会在不另行通知的情况下更改限制,因此在以此为基础进行开发之前请先核实。本文内容不构成投资建议。