401 Unauthorized after token refresh — clock skew between servers

Dealing with a very strange bug here with the CXone OAuth flow. I am implementing an automated schedule sync service using Python. The initial POST /oauth/token succeeds, returning a valid access token and a refresh_token. I store the expires_in value and set an internal timer to refresh 60 seconds before expiry.

When the timer fires, I call the refresh endpoint. The server responds with HTTP 401 Unauthorized. The error payload is minimal:

{
 "error": "invalid_grant",
 "error_description": "Token has expired"
}

I verified the refresh_token is being sent correctly in the body. My local server clock is synced via NTP. However, I suspect the CXone auth server and my app server have a clock skew exceeding the JWT validation window. The documentation here mentions a 5-minute tolerance, but this error happens immediately upon refresh.

  1. How do I verify the exact server time of the CXone auth endpoint programmatically?
  2. Is there a header or endpoint that returns the current server time to adjust my local clock?
  3. Should I be ignoring expires_in and relying solely on 401 retries instead?

You might want to check the token validation logic within your Python client. The 401 likely results from clock skew exceeding CXone’s tolerance. Ensure your system time syncs via NTP before calling cxone_auth.refresh_token(). Review CXone’s OAuth documentation for guidance on handling refresh drift and potential timing adjustments.

3 Likes