# Как синхронизироваться со временем сервера Binance API

> Подписанные запросы несут таймстамп, и Binance отклоняет их по двум разным правилам: больше 1 000 мс вперёд относительно биржевых часов или старше вашего recvWindow. Расширить вы можете только второе.

- Источник: https://papaproxy.net/ru/blog/binance-api-server-time.php
- Опубликовано: 2026-08-01
- Автор: Alex Young
- Рубрика: API и данные · Блог PapaProxy.net

---

## Ключевые выводы

- `GET /api/v3/time` (вес 1, без ключа) возвращает `serverTime` в миллисекундах — часы, по которым оценивается каждый подписанный запрос.
- Правило приёма несимметрично: жёсткий допуск в 1 000 мс для таймстампов впереди биржевых часов и recvWindow для отставших — 5 000 мс по умолчанию, 60 000 мс максимум.
- Расширение recvWindow никогда не лечит вариант `-1021` про «1000ms ahead» — это проблема часов; а Binance рекомендует держать окно в пределах 5 000 мс, потому что оно же работает окном для replay-атаки.
- Мерьте офсет часов с компенсацией круга: наивная разность `serverTime - localTime` завышала расхождение примерно на 134 мс с наших немецких адресов и на 11 мс с японских — это половина круга, а не сломанные часы.

## Как читать часы биржи

Эндпоинт серверного времени — самый простой вызов в API: `GET /api/v3/time` стоит 1 вес, не требует ключа и возвращает единственное поле `serverTime` в миллисекундах. Именно по этому числу оценивается каждый подписанный запрос.

Ошибка кроется в том, как это число сравнивают со своим. Очевидный способ — вычесть локальное время из ответа и назвать разницу расхождением часов — незаметно вкладывает в результат вашу сетевую задержку: ответ, который вы держите в руках, сгенерирован где-то за Тихим океаном и потратил половину круга на дорогу обратно. Мерьте так, как это делает NTP: возьмите вызов в скобки из локальных отметок времени и скомпенсируйте полёт.

PythonКопировать код

```python
import time, requests

def clock_offset(base="https://api.binance.com", samples=10):
    offsets = []
    for _ in range(samples):
        t0 = time.time() * 1000
        server = requests.get(f"{base}/api/v3/time").json()["serverTime"]
        t1 = time.time() * 1000
        offsets.append(server - (t0 + t1) / 2)   # середина отрезка гасит круг
        naive = server - t1                       # что показал бы наивный метод
    offsets.sort()
    return offsets[len(offsets) // 2], naive      # медианный офсет, последний наивный замер

offset, naive = clock_offset()
print(f"истинный офсет: {offset:+.0f} мс   наивная оценка: {naive:+.0f} мс")
```

На что смотреть: скомпенсированная медиана — ваше реальное расхождение часов, и на любом хосте с работающим NTP она укладывается в несколько десятков миллисекунд. Разрыв между двумя напечатанными числами — чистая география. Мы прогнали это со своей сети в июле 2026-го: с японских адресов наивный метод завышал офсет примерно на 11 мс, с немецких — примерно на 134 мс, то есть почти ровно на половину круга, замеренного для этих регионов. Европейский сервер, «продиагностированный» наивным методом, выглядит как машина со сломанными часами, хотя часы у него в порядке.

Отсюда два практических правила. Дисциплинируйте часы хоста через NTP (chrony или systemd-timesyncd), а не правкой таймстампов в коде; и если офсет всё же применяете, мерьте его периодически в фоне, а не перед каждым запросом — иначе вы тратите вес и добавляете круг в критический путь ради информации, которая меняется медленно.

## Как recvWindow решает, принять ли запрос

Каждый подписанный запрос несёт `timestamp` и может нести параметр recvWindow, который сообщает бирже, как долго эта подпись остаётся действительной. Документированную логику приёма стоит прочитать буквально, потому что она несимметрична:

КодКопировать код

```
if (timestamp < (serverTime + 1000) && (serverTime - timestamp) <= recvWindow) {
  // обработать запрос
} else {
  // отклонить запрос
}
```

Читаем слева направо. Первое условие — допуск «в будущее», и это жёсткие 1 000 мс, которые не настраиваются: запрос с таймстампом больше чем на секунду впереди биржевых часов будет отклонён при любых настройках. Второе условие — допуск «в прошлое», и вот это уже recvWindow: 5 000 мс по умолчанию, 60 000 мс максимум.

Эта асимметрия объясняет самый частый впустую потраченный вечер в интеграциях с Binance. Ошибка `-1021` приходит в двух вариантах: «Timestamp for this request was 1000ms ahead of the server's time» и «Timestamp for this request is outside of the recvWindow». Выглядят они как одна проблема, но это не так. Расширение recvWindow ничего не даёт первому сообщению — спешащие часы лечатся часами. Помогает оно только со вторым, где запрос действительно шёл слишком долго между подписью и приходом.

Отсюда вопрос, насколько широким его ставить. Рекомендация самой Binance в документации эндпоинта серверного времени — держать recvWindow небольшим, 5 000 мс или меньше, и причина здесь в безопасности, а не в производительности: окно определяет, как долго перехваченный запрос остаётся пригодным к повтору, так что 60-секундное окно — это 60 секунд возможности для replay-атаки, которые вы выдали себе сами. Считайте максимум аварийной мерой для по-настоящему плохого канала, а не значением по умолчанию. И помните, на что окно расходуется: задержка тратится из него. Подписанный ордер из Европы сжигает около 135 мс бюджета только на дорогу до Токио, ещё до того, как движок что-то сделает — внутри 5 000 мс это комфортно, но означает, что канал с редкими многосекундными зависаниями даст отказы по таймстампу, которые выглядят случайными, но таковыми не являются.

## Как проверить связь до того, как винить часы

Прежде чем грешить на часы, потратьте десять секунд на доказательство связи. Эндпоинт ping — `GET /api/v3/ping`, вес 1, пустой JSON в ответе — отвечает на один вопрос: способна ли эта машина вообще достучаться до этого API? Запустите его, затем вызов времени, затем свой подписанный запрос: первый упавший шаг и скажет, какой слой чинить.

BashКопировать код

```bash
curl -s -o /dev/null -w "%{http_code}\n" https://api.binance.com/api/v3/ping   # доступность?
curl -s https://api.binance.com/api/v3/time                                     # часы?
```

Ping падает или отдаёт неожиданный статус — проблема в сети, хосте или правомочности для вашего региона, и никакие таймстампы её не вылечат. Ping проходит, а подписанные вызовы падают с `-1021` — вот теперь часы законный подозреваемый, и замер офсета выше покажет, расхождение это или всего лишь расстояние. Ping и time проходят оба, а подписанные вызовы падают с `-1022` — это подпись, а не часы: строка параметров, которую вы подписали, не совпадает с отправленной.

Есть ещё одна причина держать эту лесенку в стартовой процедуре, а не в памяти. Отказы по таймстампу кучкуются вокруг событий, никак не связанных с кодом: виртуалка, поднятая из паузы; хост контейнера с уплывающими часами; воркер, передеплоенный в другой регион. Логирование скомпенсированного офсета и времени круга при старте, рядом с исходящим IP, превращает каждое из них в однострочный диагноз вместо вечера отладки.

Расстояние — та часть, которую действительно можно заложить в проект. На ордерном пути каждая миллисекунда между подписью и приходом тратится сразу из двух бюджетов: из recvWindow, который решает, примут ли запрос, и из проскальзывания, которое решает, сколько он стоит. Поэтому активные трейдеры выносят точку выхода поближе к матчинг-движку в Токио — и ровно для этого используют наши [прокси для Binance](/target/binance-proxy-server/): выделенные IPv4-адреса с выбором страны, статичные на весь срок плана, чтобы адрес из правил IP whitelisting оставался тем адресом, с которого вы подключаетесь.
