JWT Header vs Payload vs Signature Explained

A JWT has three Base64URL-encoded parts separated by dots: the header says how the token is signed, the payload carries the claims (who the user is, when the token expires), and the signature proves the first two parts haven't been changed since the issuer signed them. The header and payload are readable by anyone, and you can view them in the Toolstik JWT Decoder. Only the signature, checked with the right key, makes the token trustworthy.
Try it now
JWT Decoder
Loading tool…
The shape of a token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImlhdCI6MTcwMDAwMDAwMCwiZXhwIjoxNzAwMDAzNjAwfQ.tulDXbfVqDWHrXTAOEEdlUfcIeFRjIRiBiMlcRqwA6Y
Split on the dots, that's:
header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
payload: eyJzdWIiOiJ1c2VyXzEyMyIsImlhdCI6MTcwMDAwMDAwMCwiZXhwIjoxNzAwMDAzNjAwfQ
signature: tulDXbfVqDWHrXTAOEEdlUfcIeFRjIRiBiMlcRqwA6Y
Each segment is Base64URL: standard Base64 with + replaced by -, / replaced by _, and trailing = padding removed, so the token is safe in URLs and HTTP headers. You can reproduce this in the Base64 Encoder / Decoder by switching on URL-safe Base64 and clicking Decode on the first segment.
The formal names: this is a JWS (JSON Web Signature, RFC 7515) carrying a JWT claims set (RFC 7519). An encrypted token, a JWE (RFC 7516), has five segments instead and can't be read without the decryption key.
Part 1: the header
The header is a small JSON object describing the token:
{
"alg": "HS256",
"typ": "JWT"
}
| Field | Meaning |
|---|---|
alg | Signing algorithm, for example HS256, RS256, ES256, or EdDSA |
typ | Media type, usually JWT |
kid | Key ID, which tells the verifier which key from a set (often a JWKS endpoint) to use |
Base64URL-encoding {"alg":"HS256","typ":"JWT"} gives eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. Because every JSON object starts with {", which encodes to eyJ, you can recognize JWTs and other Base64 JSON at a glance.
Security note: the header is attacker-controlled input. A verifier should decide which algorithms it accepts, not take alg from the token at face value. The classic failures are accepting "alg": "none" (no signature at all) and algorithm confusion, where an RS256 public key gets used as an HS256 secret.
Part 2: the payload (claims)
The payload is a JSON object of claims. Some names are registered in RFC 7519 and have defined meanings:
| Claim | Name | Value |
|---|---|---|
iss | Issuer | Who created the token, often a URL |
sub | Subject | Who the token is about, usually a user ID |
aud | Audience | Which API or service should accept it |
exp | Expiration | Unix time in seconds after which it's invalid |
nbf | Not before | Unix time in seconds before which it's invalid |
iat | Issued at | Unix time in seconds when it was created |
jti | JWT ID | Unique ID, useful for revocation lists |
Everything else is a custom claim: roles, tenant IDs, email, scopes.
The three time claims are plain integers of seconds since 1970-01-01T00:00:00Z, not milliseconds. "exp": 1700003600 means 2023-11-14T23:13:20Z. To read them, paste the number into the Unix Timestamp Converter. The guide on how to convert a Unix timestamp covers the seconds-versus-milliseconds trap that breaks so many expiry checks.
Two consequences of the payload being merely encoded:
- Anyone holding the token can read it. Don't put secrets, passwords, or sensitive personal data in claims.
- Size adds up. The token travels with every request, often in a header or cookie. Large claim sets make every call heavier, and some servers and proxies limit header size.
Part 3: the signature
The signature is computed over the encoded header and payload joined by a dot. For HS256:
signature = base64url( HMAC-SHA256( secret, base64url(header) + "." + base64url(payload) ) )
In Node.js, this reproduces the signature of the example token (the demo secret is demo-secret, never use that for real):
const crypto = require("crypto");
const [h, p, s] = token.split(".");
const expected = crypto.createHmac("sha256", "demo-secret").update(`${h}.${p}`).digest("base64url");
console.log(expected === s); // true
Change one character in the payload, such as a different sub or a later exp, and the expected signature no longer matches. That's the whole security model: integrity and authenticity, not secrecy.
Symmetric vs asymmetric algorithms
| Family | Examples | Signs with | Verifies with | Good for |
|---|---|---|---|---|
| HMAC | HS256, HS384, HS512 | Shared secret | Same secret | One service that issues and checks its own tokens |
| RSA | RS256, PS256 | Private key | Public key | Identity providers and many verifying services |
| Elliptic curve | ES256, EdDSA | Private key | Public key | Same as RSA, with smaller keys and signatures |
With HMAC, every service that can verify can also mint tokens. With RSA or EC, you can publish the public key (often at a /.well-known/jwks.json URL) and only the issuer holds the private key.
How the three parts work together
- The issuer builds the header and payload, encodes them, signs them, and joins all three with dots.
- The client stores the token and sends it, typically as
Authorization: Bearer <token>. - The API splits the token, recomputes or checks the signature with its key, rejects disallowed algorithms, then checks
exp,nbf,iss, andaud. - Only after all of that does the API trust the claims.
A decoder, Toolstik's included, does only the split-and-read part of step 3. That's why the JWT Decoder shows a reminder that it doesn't verify signatures, and why it never asks for your secret.
The decoder's Copy header and Copy payload buttons give you the JSON as text, which is handy when you want to search a claim or paste exp into the Unix Timestamp Converter.
Putting it into practice
To inspect a real token step by step, including the safety checks before you paste and fixes for common decode errors, follow how to decode a JWT safely. Once the header and payload are on screen, you can copy the JSON into the JSON Formatter if you want to reformat it. The rest of the browser-based toolkit (JSON, Base64, URL encoding, timestamps, and regex) is collected in free developer tools online.
Frequently asked questions
Tools in this guide
JWT Decoder
Decode JWT header and payload locally. Inspect claims—signature is not verified.
Base64 Encoder / Decoder
Encode or decode text to Base64 with optional URL-safe output—all in your browser.
Unix Timestamp Converter
Convert Unix timestamps to readable dates and dates back to epoch seconds or milliseconds.
Keep reading
- DeveloperHow to Decode a JWT SafelyHow to decode a JWT safely: inspect the header and payload locally in your browser, read exp and iat, and avoid the mistake of trusting unverified claims.
- DeveloperHow to Convert a Unix Timestamp to a Date (and Back)How to convert a Unix timestamp to a readable date and back: seconds vs milliseconds, time zones, the 2038 problem, and copy-ready code for JS, Python, SQL, and bash.
- DeveloperFree Developer Tools Online: JSON, JWT, Base64, URL, Timestamp & RegexFree developer tools online that run in your browser: format JSON, decode JWTs, encode Base64 and URLs, convert Unix timestamps, and test JavaScript regex.