OKX API error 50102 "Timestamp request expired" and how to fix it
Figures on this page are as of 2026-09. Fees, limits and margin tiers change — check the venue's own docs.
OKX error 50102 comes back with HTTP 401 and the message "Timestamp request expired." It means the OK-ACCESS-TIMESTAMP header on your request is
more than 30 seconds away from OKX’s server time, so OKX treats the request as
stale and refuses it.
The fix is almost never in your signing code. OKX’s documentation names the most common culprit — a local timezone offset — and you can confirm or rule it out in a single request. This page is for anyone hand-rolling OKX V5 authentication, or debugging a library that does it for them.
What this error actually means
Every private REST request to OKX carries four headers (the full set is in
the OKX passphrase and four-header model). One of
them is OK-ACCESS-TIMESTAMP, and the same value is the first component of the
string you sign:
timestamp + method + requestPath + body
The timestamp is there to stop replay: a captured request re-sent later falls outside the window, and because the timestamp is signed it cannot be edited in transit. (The general mechanism is in what is HMAC request signing.)
OKX states the window in its authentication section: the server rejects requests where the timestamp “differs from server time by more than 30 seconds (error 50102)”. Note the word differs. A timestamp too far in the future fails just as a stale one does.
The format matters as much as the value. OKX wants ISO 8601, in UTC, with milliseconds:
2020-12-08T09:08:57.715Z
That is unusual. Many HMAC-signed exchange APIs — Binance and Bybit among them — take an integer count of milliseconds since the Unix epoch; see clock sync and timestamps for the cross-venue picture. Code ported from one of those, or written from memory of them, is where OKX timestamps usually go wrong. A formatted string has more ways to be wrong than an integer does, and one of them is quiet.
The usual cause: local time labelled as UTC
OKX’s docs say local timezone offset is “the most common cause” of 50102.
Here is how that happens.
The code takes the machine’s local wall-clock time, formats it as ISO 8601 and
appends Z — or hands it to a formatter that assumes the value is already UTC.
The result is a perfectly well-formed string that claims to be UTC but is
actually local time. On a machine set to UTC+8, every timestamp is eight hours
in the future. OKX compares that to its own clock, sees a gap measured in hours,
and returns 50102.
The fingerprints are distinctive:
- It fails on every request, not intermittently. A timezone offset is far larger than a 30-second window, so nothing ever squeaks through.
- It works on one machine and fails on another — typically fine on a server configured for UTC, broken on a laptop that is not, or the reverse after a deploy.
- The gap, when you measure it, is close to your UTC offset.
In Python, the bug and the fix look like this:
from datetime import datetime, timezone
# Wrong: local wall-clock time, labelled as UTC
ts = datetime.now().isoformat(timespec='milliseconds') + 'Z'
# Right: an aware UTC time, with milliseconds, in the docs' 'Z' form
ts = datetime.now(timezone.utc).isoformat(timespec='milliseconds')
ts = ts.replace('+00:00', 'Z')
In JavaScript, new Date().toISOString() already returns UTC with milliseconds
and a trailing Z. Whatever the language, match the shape of OKX’s example
exactly rather than relying on an equivalent form being accepted.
Other causes, in order of likelihood
1. The machine’s clock has drifted
Virtual machines after suspend or migration, containers inheriting a wrong host clock, and minimal images without NTP are the usual sources. Drift has to be substantial to cross 30 seconds, so it shows up as something that worked yesterday failing today — intermittently at first. The general remedies are in clock sync and timestamps.
2. The request was signed, then waited
The timestamp is set when you sign, not when the bytes leave. Anything that separates the two can push a correct timestamp out of the window:
- a client-side rate limiter or queue that holds signed requests;
- a job that signs a batch of requests up front and sends them over a minute;
- a retry that re-sends the same signed request after a backoff.
The last is the subtle one: the original attempt was fine, the retry fails with
50102, and the log makes it look as though the clock broke between attempts.
Re-sign on every attempt. And for order placement specifically, a retry
needs more thought than a fresh timestamp — see
why retrying a failed order is not safe.
3. REST and WebSocket share a timestamp helper
WebSocket login is signed differently. For the login message, OKX’s docs specify
a timestamp in Unix epoch seconds — not ISO 8601 — with method always
GET and requestPath always /users/self/verify, and the login expires 30
seconds after its timestamp. A single “make timestamp” helper shared between the
two channels will be wrong for one of them. Keep them separate and name them for
the channel.
50102 and its neighbours
Three codes in OKX’s error table are easy to confuse, and they point at different fixes:
| Code | HTTP | OKX’s message | What it points at |
|---|---|---|---|
50102 | 401 | “Timestamp request expired.” | A timestamp that parses but is more than 30 s from server time |
50112 | 401 | “Invalid OK-ACCESS-TIMESTAMP.” | The timestamp header itself is not acceptable |
50113 | 401 | “Invalid signature.” | The signature does not match what OKX computed |
Source: OKX’s V5 error-code table.
The docs do not spell out exactly which malformed strings produce 50112 rather
than 50102. Practically it does not matter: if what you send is not ISO 8601
UTC with milliseconds, fix the format first, whichever code you got.
One timestamp bug lands on the signature code instead. If you generate the
timestamp twice — once for the signature, once for the header — both values are
inside the window, but they differ by a few milliseconds and the signature can
never match. That is 50113 territory; see
OKX API error 50113.
How to confirm which one you have
- Ask OKX what time it is.
GET /api/v5/public/timeis public and needs no key. OKX’s docs recommend synchronising against it before placing orders precisely to avoid50102. - Log the exact timestamp string your client sent — the header value, not a value you recompute afterwards.
- Convert both to the same representation and subtract.
- Read the gap:
- Hours, close to your UTC offset → local time labelled as UTC.
- Seconds to minutes, growing over days → clock drift; fix NTP.
- Near zero at signing time, yet the request failed → the request sat somewhere before sending; look at queues and retries.
- Near zero, but the string does not look like
2020-12-08T09:08:57.715Z→ a format problem, which may be reported as50112.
Do this check once at process startup and log the delta. It costs one request and turns a mystery into a number.
Correcting the system clock is the real fix. If you also apply an offset
measured from /api/v5/public/time, re-measure it periodically and alert on a
large one rather than absorbing it silently.
Why this should fail loudly
50102 does not clear on retry. Retrying with the same timestamp fails; retrying
with a fresh timestamp from the same broken clock fails too. An integration that
feeds it into a generic retry loop just burns rate limit. Treat it as a
configuration fault that stops the process — the classification is laid out in
error handling patterns for trading APIs.
The same applies when a model is calling tools: it will naturally try again, and
neither the model nor the retry can fix a clock.
FAQ
What does OKX error 50102 mean?
It means the OK-ACCESS-TIMESTAMP on your request differs from OKX’s server
time by more than 30 seconds, so OKX rejects the request as expired. It comes
back with HTTP 401 and the message “Timestamp request expired.” OKX’s docs name
a local timezone offset as the most common cause.
Why does OKX return 50102 on my laptop but not on my server?
Most likely your code formats local time as if it were UTC. A server set to UTC hides the bug because local time and UTC coincide there; a machine in another timezone produces timestamps off by its UTC offset, far outside OKX’s 30-second window. Generate the timestamp from an explicit UTC clock.
What timestamp format does the OKX API require?
For REST, ISO 8601 in UTC with milliseconds, for example
2020-12-08T09:08:57.715Z, and the same value must appear in the signature and
in the OK-ACCESS-TIMESTAMP header. WebSocket login is different: it uses Unix
epoch seconds, and the login expires 30 seconds after that timestamp.
Will retrying fix OKX error 50102?
No. The cause is the clock or the timestamp format, and a retry reuses one or the
other. Fix the timestamp generation or synchronise the clock — checking the gap
against GET /api/v5/public/time shows which — and re-sign each request at the
moment it is sent.