json

FormatValidateConvert

In this sectionJWT

Guide to a workspace

The JWT decoder

Read a token’s header and claims, see whether its times still hold, check its signature with a secret or a public key, and take the payload on to the editor.

3 jobs · 5 guidesEvery example is checked against the real engine

Read a token

Decode a token: its parts, header and payload

Input

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Do

  1. Open Settings and set Indent to 2 spaces; the decoder writes the Header and Payload cards with it.
  2. Paste the token into the Token field.
  3. Read the Header, Payload and Claims cards on the right.

Result

Header
{
  "alg": "HS256",
  "typ": "JWT"
}

Payload
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

Claims
sub   Subject    "1234567890"
name  Name       "John Doe"
iat   Issued at  1516239022  2018-01-18T01:30:22Z

No expiry: the token has no exp claim

Try it in the decoder →

The token field colours each part as you paste: the header, the payload and the signature, split at the dots. Under the field a line names the form, Signed (JWS) · 3 parts here. On the right, Header and Payload show the two JSON parts, read-only, each with a Copy button, and Claims lays the payload out as a table.

The table’s first column is the claim’s name with its meaning beside it, so sub reads as Subject and iat as Issued at. A string keeps its quotes, which is how you tell the text "1234567890" from a number. A time is written out in UTC next to the raw seconds. The page also says how long ago that was; the block above leaves it off, because it changes every day. This token has no exp, and the status line says so. No key was needed for any of it: the Verify card just asks for the shared secret.

Is it still valid? The times and the status line

Input

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTQ4MjEiLCJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJhdWQiOiJvcmRlcnMtYXBpIiwiaWF0IjoxNzY3MjI1NjAwLCJuYmYiOjE3NjcyMjU2MDAsImV4cCI6MTc2NzIyOTIwMH0.Qq_ovlXB4QQcgh2ntlukqqSnORfAyCXcmQVLjnpcJsE

Do

  1. Open Settings and set Indent to 2 spaces; the decoder writes the Header and Payload cards with it.
  2. Paste the token into the Token field.
  3. Read the Claims card, and the status line above its table.

Result

Header
{
  "alg": "HS256",
  "typ": "JWT"
}

Payload
{
  "sub": "user-4821",
  "iss": "https://auth.example.com",
  "aud": "orders-api",
  "iat": 1767225600,
  "nbf": 1767225600,
  "exp": 1767229200
}

Claims
sub  Subject     "user-4821"
iss  Issuer      "https://auth.example.com"
aud  Audience    "orders-api"
iat  Issued at   1767225600  2026-01-01T00:00:00Z
nbf  Not before  1767225600  2026-01-01T00:00:00Z
exp  Expires     1767229200  2026-01-01T01:00:00Z

Try it in the decoder →

Three claims decide when a token may be used: iat, when it was issued; nbf, the moment before which it is not yet good; and exp, when it runs out. This one was issued at midnight on the first of January 2026 and ran out an hour later, so on the live page the line above the table reads Expired followed by how long ago, and it is drawn as a warning.

That line takes four shapes: expired, not valid until a given time, active with the time left, or no expiry. It counts forward on its own while the tab is in view, so a token about to lapse turns expired in front of you. Hovering a time shows it in your own zone. There is no grace period of a minute or two, as many servers allow: the decoder does the plain arithmetic, and a claim that breaks its own rules, such as an iat in the future, is flagged in its row.

Check its signature

Verify an HS token with its secret

Input

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Key

your-256-bit-secret

Do

  1. Paste the token into the Token field.
  2. Paste the key into Secret or public key, and leave Secret is base64url unticked.
  3. Replace the key with your-256-bit-secreT, one letter changed.

Result

With the secret it was signed with
Signature verified
HS256 · HMAC secret, 19 bytes (UTF-8)
The HMAC secret is 19 bytes; HS256 recommends at least 32

With one letter changed
Invalid signature
HS256 · HMAC secret, 19 bytes (UTF-8)
The HMAC secret is 19 bytes; HS256 recommends at least 32

Try it in the decoder →

An HS256, HS384 or HS512 token is signed with a secret that the issuer and the checker share. Paste it into Secret or public key and the answer follows a moment after you stop typing. The first line is the verdict; the second names the algorithm and what the key was read as, here nineteen bytes of text. Secret is base64url appears only for these three algorithms, for a secret your configuration stores encoded.

One changed letter and the verdict turns to Invalid signature, with the same detail: the decoder read the key perfectly well, and the signature simply does not match it. The line under both answers is a notice, not an error. The secret jwt.io made famous is shorter than HS256 asks for, so a token signed with it is easier to forge by guessing, and the decoder says so while still giving the verdict.

Verify with a public key: PEM, certificate, JWK or JWKS

Input

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im9yZGVycy0yMDI2In0.eyJzdWIiOiJ1c2VyLTQ4MjEiLCJzY29wZSI6Im9yZGVyczpyZWFkIiwiaWF0IjoxNzY3MjI1NjAwfQ.cwbAlGcRvn7uQi5b4tV7ovEU-HgLOC6AAIE4naXS92Zw0QeXINYVF5bTudrlzpuM9NF8m0vQOZ_aMHBg1TI7ig

Key

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7/rqlSpW6MSckPkFYgX6pwZzTdP2
Og5CR19tddt/ubi+toicBJwOFUrq22jK5p/rCtH7JdsePhUYrIf+vPK3AQ==
-----END PUBLIC KEY-----

Key 2

{
  "kty": "EC",
  "crv": "P-256",
  "x": "7_rqlSpW6MSckPkFYgX6pwZzTdP2Og5CR19tddt_ubg",
  "y": "vraInAScDhVK6ttoyuaf6wrR-yXbHj4VGKyH_rzytwE"
}

Do

  1. Paste the token into the Token field.
  2. Paste Key, the PEM, into Secret or public key.
  3. Replace it with Key 2, the same public key as a JWK.

Result

With the PEM
Signature verified
ES256 · EC P-256 public key (PEM)

With the JWK
Signature verified
ES256 · EC P-256 public key (JWK)

Try it in the decoder →

RS, PS, ES and EdDSA tokens are signed with a private key and checked with its public half, which the issuer is happy to publish. The field takes it as a PEM block, as the certificate that carries it, as one JWK, or as a whole JWKS, where the decoder picks the key whose kid matches the header’s. The detail line says which shape it read, so the two runs above differ only in their last word.

Three refusals protect you here, and the decoder’s own article explains each. A public key offered for an HS token is refused, because that is the algorithm confusion forgery. A key the token carries in its own header is never used, since a token cannot vouch for itself. And a key weaker than its algorithm asks for still verifies, with a notice.

Carry it on

Open the payload in the editor

Input

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Do

  1. Open Settings and set Indent to 2 spaces; the decoder writes the Header and Payload cards with it.
  2. Paste the token into the Token field.
  3. Press Open payload in the editor, at the foot of the Payload card.

Result

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

Try it in the decoder →

Open payload in the editor sits at the foot of the Payload card, and appears only when the payload is JSON. It carries the claims across as a document called jwt-payload.json, written with your indent, and the editor names the decoder as where it came from. If the editor’s left pane already holds something, you are asked before it is replaced.

The token and the key stay behind. Only the decoded claims travel, and only on that press, so what reaches the editor is data you can read anyway rather than a credential. From there the claims are a document like any other: a tree to fold, a table to sort, or the left pane of a Schema check that holds them to the shape your service expects.

About these guides

A JSON Web Token turns up wherever a service has to prove who is calling: in an Authorization header, a cookie, the answer a sign-in page sends back. It looks like noise, but most of it is JSON you can read. The JWT decoder takes one apart on the spot, and answers the three questions people usually bring to it: what does this token say, is it still good, and was it really signed by the key I think it was?

Those three questions are the jobs above, with a fourth for what comes after. Reading a token needs nothing but the token. Checking its signature needs the secret it was signed with, or the public half of the key pair, and the decoder tells you which one it wants before you paste anything. Taking the claims on to the editor is one press.

Every example prints what the decoder shows for its token, produced by the code the page runs. Try it in the decoder under each one opens the decoder with the token already in the field and, on a signature example, the key already in the Verify card, so the verdict appears without a press. That is the only way a token reaches the decoder already filled in: it is read once and deleted, and a reload leaves both fields empty again.

Before you trust a token

Decoding is not checking. Anyone can write a header and a payload and encode them, so a token that reads perfectly may still be a forgery. Treat the claims as a claim until the Verify card says Signature verified against a key you got from the issuer, not from the token.

Paste the public half only. Verifying never needs a private key, and a web page is the wrong place for one. The decoder keeps the token and the key in the tab’s memory alone: nothing is saved, and closing or reloading the tab is enough to forget them.

The palette, the keys, Settings and the theme work the same on every tool, and Around the app covers them once for all of them. Open the decoder, or Open the editor to work on a payload you have already copied out.