Bybit API Timestamp Error: Fix It and Keep Trading
The Bybit API timestamp error (code 10002) stops your trading bot cold. Learn why it happens, how to detect clock drift, and fix it permanently with Python.
The Bybit API timestamp error (code 10002) stops your trading bot cold. Learn why it happens, how to detect clock drift, and fix it permanently with Python.
Your trading bot just threw error code 10002 at 3 AM and missed a clean entry. The Bybit API timestamp error is one of the most common — and most fixable — problems algo traders face. It has nothing to do with your API keys being wrong, your strategy being broken, or Bybit's servers going down. It is a clock problem. Bybit, like Binance, OKX, and most professional crypto exchanges, requires that the timestamp in your request falls within a narrow window of the server's current time — typically ±5000 milliseconds. If your machine's clock drifts even slightly outside that window, every authenticated request gets rejected instantly. The good news: once you understand why it happens and implement proper time synchronization, it almost never comes back.
Every authenticated request to Bybit's API — placing an order, checking your balance, canceling a position — must include a timestamp parameter in Unix milliseconds. Bybit's servers compare that value against their own system clock the moment the request arrives. If the absolute difference exceeds the recv_window tolerance (default 5000ms), the request is rejected before any further processing occurs.
# Typical 10002 error response from Bybit V5 API
{
"retCode": 10002,
"retMsg": "timestamp is out of date",
"result": {},
"retExtInfo": {},
"time": 1715123456789
}
This is a deliberate security mechanism, not a bug or server instability. Without timestamp validation, a malicious actor who captured your signed HTTP request could replay it hours or days later — placing unauthorized orders or withdrawals on your account. Platforms like Bybit and OKX enforce this across every authenticated endpoint without exception. The recv_window parameter (Bybit default: 5000ms, maximum: 60000ms) defines how much tolerance the server grants. Increasing it buys breathing room during high-latency moments, but it does not fix an underlying clock drift — it just masks it until the drift grows large enough to break through even a generous window.
Error code 10002 always means a timestamp mismatch — nothing else. Consistent 10002 errors mean your clock has drifted. Intermittent 10002 errors mean you are close to the boundary and network latency is pushing you over it. Both are fixed the same way: synchronize your clock.
Your computer's hardware clock — the Real-Time Clock (RTC) — runs slightly fast or slow, and this inaccuracy compounds over time. For a desktop machine that syncs via NTP regularly, this is invisible. For a cloud VM running a 24/7 trading bot, it can become catastrophic. When a virtual machine is paused, suspended, or live-migrated between physical host servers by the cloud provider, its clock can jump by seconds or minutes with no notification to the operating system. This is the single most common cause of Bybit API timestamp errors for traders running bots on VPS providers like DigitalOcean, Vultr, Hetzner, and AWS EC2.
The drift threshold is surprisingly small. A 6-second drift causes 100% failure. A 2–3 second drift causes intermittent failures that are maddening to debug because they look random. On AWS EC2, Google Cloud Compute Engine, and DigitalOcean Droplets, the platform provides a dedicated time sync service that is more reliable than public NTP pools. AWS exposes the Amazon Time Sync Service at 169.254.169.123 — a link-local address that is always reachable from any EC2 instance regardless of VPC configuration. Configuring your bot server to use this endpoint is a five-minute fix that eliminates timestamp errors permanently on AWS infrastructure.
Before reaching for any fix, confirm you actually have a clock drift problem. Bybit exposes a public time endpoint that requires no authentication — it is perfect for a quick sanity check. Compare your local timestamp against Bybit's server time and measure the difference. This test takes under a second to run and tells you exactly how bad the situation is.
import time
import requests
def check_clock_drift():
"""Compare local system clock against Bybit server time."""
local_ts_ms = int(time.time() * 1000)
resp = requests.get("https://api.bybit.com/v5/market/time", timeout=5)
data = resp.json()
# timeSecond is Unix seconds; convert to milliseconds
server_ts_ms = int(data["result"]["timeSecond"]) * 1000
drift_ms = local_ts_ms - server_ts_ms
print(f"Local timestamp : {local_ts_ms}")
print(f"Bybit server time: {server_ts_ms}")
print(f"Clock drift : {drift_ms:+d}ms")
if abs(drift_ms) < 1000:
print("Status: OK — clock is well synchronized")
elif abs(drift_ms) < 5000:
print("Status: WARNING — drift approaching limit, fix soon")
else:
print("Status: CRITICAL — all authenticated API calls will fail")
return drift_ms
drift = check_clock_drift()
Run this diagnostic on your bot server before anything else. If drift is consistently above 1000ms, you have a real OS-level clock problem. If the drift fluctuates wildly between consecutive runs (jumping from +200ms to +4800ms), your NTP service is running but unreliable — possibly connecting to a distant or overloaded NTP server. For reference: Binance exposes GET /api/v3/time returning serverTime in milliseconds, and OKX exposes GET /api/v5/public/time returning ts as a millisecond string. You can run the same diagnostic against either exchange to confirm the issue is local to your machine, not specific to Bybit.
There are three distinct strategies, and the right choice depends on your environment. They are ordered from best (fix the root cause) to last resort (increase tolerance). Use the first approach that fits your situation rather than layering all three on top of each other.
Approach 1 — Fix the OS clock. This is always the correct long-term fix. On Linux VPS servers, force an immediate NTP sync and verify it worked:
# Check current sync status
timedatectl status
# Force immediate sync with chrony (most Linux distros)
sudo chronyc makestep
# Or restart systemd-timesyncd
sudo systemctl restart systemd-timesyncd
# AWS EC2 — use Amazon Time Sync Service for best reliability
sudo sed -i 's/^#NTP=/NTP=169.254.169.123/' /etc/systemd/timesyncd.conf
sudo systemctl restart systemd-timesyncd
# Verify NTP sync is active
timedatectl show | grep NTPSynchronized
# Should return: NTPSynchronized=yes
Approach 2 — Apply a server-time offset in your bot code. This is the right choice when you cannot control the server OS (containerized environments, managed hosting, CI/CD runners). Fetch Bybit's server time once at startup, calculate the offset from your local clock, and apply that offset to every request timestamp going forward. This approach works in any environment and is independent of NTP configuration:
import time
import hmac
import hashlib
import requests
API_KEY = "your_api_key_here"
API_SECRET = "your_api_secret_here"
BASE_URL = "https://api.bybit.com"
def get_server_time_offset() -> int:
"""Return the ms offset: server_time - local_time."""
resp = requests.get(f"{BASE_URL}/v5/market/time", timeout=5)
server_ms = int(resp.json()["result"]["timeSecond"]) * 1000
return server_ms - int(time.time() * 1000)
# Calculate once at startup
time_offset_ms = get_server_time_offset()
print(f"Applied time offset: {time_offset_ms}ms")
def get_synced_timestamp() -> int:
return int(time.time() * 1000) + time_offset_ms
def sign_request(timestamp: int, recv_window: int, params: str) -> str:
payload = f"{timestamp}{API_KEY}{recv_window}{params}"
return hmac.new(
API_SECRET.encode("utf-8"),
payload.encode("utf-8"),
hashlib.sha256
).hexdigest()
def get_wallet_balance() -> dict:
timestamp = get_synced_timestamp()
recv_window = 5000
query_string = "accountType=UNIFIED"
headers = {
"X-BAPI-API-KEY": API_KEY,
"X-BAPI-TIMESTAMP": str(timestamp),
"X-BAPI-SIGN": sign_request(timestamp, recv_window, query_string),
"X-BAPI-RECV-WINDOW": str(recv_window),
}
resp = requests.get(
f"{BASE_URL}/v5/account/wallet-balance",
headers=headers,
params={"accountType": "UNIFIED"}
)
return resp.json()
print(get_wallet_balance())
Even a well-calibrated offset can drift over days of continuous operation. The most resilient bots do not assume the initial sync remains accurate forever — they detect a 10002 response, refresh the offset, and retry transparently. This pattern is especially valuable for bots that act on real-time signals, where a missed retry means a missed trade. The class below wraps all of this into a clean, reusable client:
import time
import hmac
import hashlib
import requests
from typing import Optional
class BybitClient:
SYNC_INTERVAL_SEC = 300 # Re-sync every 5 minutes
def __init__(self, api_key: str, api_secret: str):
self.api_key = api_key
self.api_secret = api_secret
self.base_url = "https://api.bybit.com"
self._time_offset_ms = 0
self._last_sync_ts = 0
self._sync_time()
def _sync_time(self):
try:
resp = requests.get(f"{self.base_url}/v5/market/time", timeout=5)
server_ms = int(resp.json()["result"]["timeSecond"]) * 1000
self._time_offset_ms = server_ms - int(time.time() * 1000)
self._last_sync_ts = time.time()
print(f"[BybitClient] Time synced. Offset: {self._time_offset_ms}ms")
except Exception as exc:
print(f"[BybitClient] Time sync failed: {exc}")
def _timestamp(self) -> int:
if time.time() - self._last_sync_ts > self.SYNC_INTERVAL_SEC:
self._sync_time()
return int(time.time() * 1000) + self._time_offset_ms
def _sign(self, timestamp: int, recv_window: int, body: str) -> str:
payload = f"{timestamp}{self.api_key}{recv_window}{body}"
return hmac.new(
self.api_secret.encode("utf-8"),
payload.encode("utf-8"),
hashlib.sha256
).hexdigest()
def get(
self,
path: str,
params: Optional[dict] = None,
recv_window: int = 5000,
retries: int = 3
) -> dict:
params = params or {}
for attempt in range(retries):
ts = self._timestamp()
query = "&".join(f"{k}={v}" for k, v in sorted(params.items()))
headers = {
"X-BAPI-API-KEY": self.api_key,
"X-BAPI-TIMESTAMP": str(ts),
"X-BAPI-SIGN": self._sign(ts, recv_window, query),
"X-BAPI-RECV-WINDOW": str(recv_window),
}
resp = requests.get(
f"{self.base_url}{path}",
headers=headers,
params=params,
timeout=10
)
data = resp.json()
if data.get("retCode") == 10002:
print(f"[BybitClient] Timestamp error (attempt {attempt + 1}), resyncing...")
self._sync_time()
time.sleep(0.3)
continue
return data
return {"retCode": -1, "retMsg": "Max retries exceeded after timestamp errors"}
# Usage
client = BybitClient(api_key="YOUR_KEY", api_secret="YOUR_SECRET")
balance = client.get("/v5/account/wallet-balance", {"accountType": "UNIFIED"})
print(balance)
This client syncs automatically every 5 minutes, retries on 10002 with a fresh offset, and exposes clean typed methods. Traders using VoiceOfChain for real-time market signals can wire this client directly to signal callbacks — when a signal fires, the bot executes without worrying about timestamp drift taking it offline at a critical moment.
Approach 3 (last resort only): increase recv_window to 10000–20000ms via the X-BAPI-RECV-WINDOW header. This does not fix drift — it just widens the acceptance window. Bybit allows a maximum of 60000ms. Use this only as a temporary measure while you deploy a proper clock fix. Note: Binance calls the same parameter recvWindow, OKX uses a similar header, and Gate.io and KuCoin implement equivalent mechanisms — check each platform's API docs for their specific parameter names and maximum values.
The Bybit API timestamp error looks mysterious until you understand it — then it becomes one of the most straightforward problems in algo trading to fix permanently. At its core, it is a clock problem with a clock solution. Fix the OS clock on your server using NTP or chrony, add a server-time offset calculation to your bot code, build automatic re-sync into any long-running process, and this error will stop interrupting your trading. The same principles apply across every major exchange: whether you are trading on Bybit, Binance, OKX, Bitget, or Gate.io, timestamp validation is universal and the fix is always the same — keep your clocks synchronized, and your bots will keep running.