Blog
Wild & Free Tools

Decode JWT exp and iat Timestamps — Check When Tokens Expire

Last updated: April 2026 6 min read

Table of Contents

  1. JWT Time Claims
  2. Common Bugs
  3. Validation Patterns
  4. Debugging Workflow
  5. Frequently Asked Questions

JWT (JSON Web Token) authentication breaks for one reason more than any other: clock skew between systems. The token has an exp claim that is a Unix timestamp. Your server checks current time against that timestamp. If they disagree by more than a few seconds, the token is rejected and the user gets logged out.

Decoding the exp and iat values is the first step in debugging any JWT auth issue. This guide shows how, plus the common patterns for token timing bugs.

The Three Time Claims in a JWT

RFC 7519 defines three timestamp claims for JWT. All three are Unix timestamps in seconds (10 digits, not milliseconds — this trips up JavaScript developers constantly).

ClaimMeaningRequired
iatIssued At — when the token was createdNo, but typically present
expExpiration — token is invalid after this timeStrongly recommended
nbfNot Before — token is invalid before this timeOptional, rarely used

To decode these you do not need the signing key — JWT payloads are base64-encoded JSON, not encrypted. You can read them with any base64 decoder. The signature only matters for verifying authenticity, not for inspecting contents.

Quick decode

Use the free JWT decoder to paste a token and see the header, payload, and timestamps. Then paste the exp and iat values into the Unix timestamp converter for human-readable dates.

Common JWT Timing Bugs

Bug 1: Server clock skew

Token issuer and validator have different system clocks. Server A issues a token with iat = now. Server B receives it three seconds later but its clock is six seconds behind. Server B sees iat in the future, rejects the token as "not yet valid". The fix is to sync clocks with NTP and add a small leeway window (typically 30-60 seconds) when validating.

Bug 2: Milliseconds vs seconds

Some libraries get the JWT time format wrong. The spec says seconds. JavaScript developers default to milliseconds. A token with exp: 1711000000000 is a token that expires in the year 56000 — clearly wrong, but the validator will accept it as "not expired" forever. The reverse bug (a token with exp in seconds being interpreted as milliseconds) makes the token expire 1000 times sooner than intended.

Bug 3: Timezone confusion

Unix timestamps have no timezone. They are always the count of seconds since 1970 UTC. If your code does new Date(exp).toLocaleString() and the server is in EST, the displayed string is in EST but the underlying value is still UTC. This is fine for display but causes bugs when developers manually compute "expires in 1 hour from now" using local time arithmetic.

Bug 4: exp in the past at issue time

Refresh logic that subtracts seconds instead of adding them, or that uses signed integers and overflows. Symptom: tokens are expired the instant they are issued. Easy to spot by decoding a fresh token and checking exp against current time.

Sell Custom Apparel — We Handle Printing & Free Shipping

Validation Patterns Across Languages

// JavaScript / Node.js
const decoded = JSON.parse(atob(token.split('.')[1]));
const now = Math.floor(Date.now() / 1000);
const isExpired = decoded.exp < now;
const expiresIn = decoded.exp - now; // seconds until expiry

// Python
import json, base64
payload = json.loads(base64.urlsafe_b64decode(token.split('.')[1] + '=='))
now = int(time.time())
is_expired = payload['exp'] < now

// Go
import "github.com/golang-jwt/jwt/v5"
parsed, _ := jwt.Parse(tokenStr, keyFunc)
exp, _ := parsed.Claims.GetExpirationTime()
isExpired := exp.Before(time.Now())

All three patterns do the same thing: decode the payload, extract exp, compare to current time. The actual JWT libraries do this for you with a leeway window built in, but if you are debugging an auth issue you often need to do it manually to see the raw values.

Adding leeway for clock skew

const LEEWAY_SECONDS = 30;
const isExpired = decoded.exp + LEEWAY_SECONDS < now;

30 seconds is the typical default. Higher values reduce clock-skew rejections but expand the window during which a stolen token still works after it should have expired. Pick based on how strict your security model is.

How to Debug a "Token Expired" Error

  1. Get a fresh failing token. Reproduce the error and capture the exact JWT string from the request headers.
  2. Decode the payload. Paste the token into a JWT decoder. You will see a JSON object with the claims.
  3. Extract iat and exp. Both should be 10-digit Unix timestamps. If you see 13 digits, you have a milliseconds bug.
  4. Convert iat to a human date. Paste it into the timestamp converter. Confirm it matches when you actually requested the token (within a few seconds).
  5. Convert exp to a human date. Confirm it is in the future relative to when you sent the failing request.
  6. Check the validator's clock. SSH into the validating server, run date, compare to your local clock and to the iat value. Any drift over 30 seconds is suspicious.
  7. Check leeway settings. Look at the JWT library config in the validator. The default leeway varies by library.

Most "token expired" errors turn out to be clock skew between servers, not actual expiration. NTP everywhere fixes 80% of these. The remaining 20% are usually milliseconds-vs-seconds bugs in custom token issuance code.

For a quick decode of any token, the JWT decoder shows the payload instantly without sending the token to a server.

Try It Free — No Signup Required

Runs 100% in your browser. No data is collected, stored, or sent anywhere.

Open Free Unix Timestamp Converter

Frequently Asked Questions

What is the JWT exp claim?

exp (expiration time) is a JWT claim that contains a Unix timestamp in seconds. After this time, the token is considered invalid and validators should reject it. It is defined in RFC 7519 as a registered claim and is the standard way JWTs encode their expiration.

What is the difference between iat and exp in a JWT?

iat (issued at) is the Unix timestamp when the token was created. exp (expiration) is when the token becomes invalid. Both are seconds since the Unix epoch. The difference between them is the token lifetime — typically 15 minutes for access tokens and 7-30 days for refresh tokens.

Why does my JWT expire immediately after issuing?

Two common causes. First, the issuer set exp in the past due to a code bug (subtracting instead of adding, or wrong integer overflow). Second, the issuer used milliseconds instead of seconds for exp, causing the validator to interpret it as a date 1000 times sooner than intended. Decode the token and check the exp value.

Can I change a JWT exp without re-signing the token?

No. The exp is part of the signed payload. Changing it invalidates the signature, which any properly-configured validator will reject. The only way to extend a token is to issue a new one with a later exp — that is what refresh tokens are for.

How do I check if a JWT is expired without using a library?

Decode the payload (it is base64url-encoded JSON, not encrypted). Read the exp field. Compare to the current Unix timestamp. If exp is less than now, the token is expired. This is exactly what JWT libraries do internally — there is no magic.

What is JWT leeway and why does it matter?

Leeway is a small time window (usually 30-60 seconds) added when validating exp and iat to account for clock skew between servers. Without it, every minor clock drift causes valid tokens to be rejected. Too much leeway expands the window during which stolen tokens still work.

Launch Your Own Clothing Brand — No Inventory, No Risk