---
title: "Shield Offline Token Verification"
description: "Verify Shield tokens locally with zero network calls — tokens are Ed25519-signed JWTs, and the public key is published at a standard JWKS endpoint."
url: https://edge.network/docs/shield/offline-verification/
---

# Shield Offline Token Verification

Shield

# Offline Token Verification

Shield response tokens are Ed25519-signed JWTs. The public key is published at a
standard JWKS endpoint, so your server can verify tokens locally — no network round
trip, no added latency, no dependency on shield.edge.network being reachable.

## The Key Endpoint

```
GET https://shield.edge.network/.well-known/jwks.json

{
  "keys": [
    {
      "kty": "OKP",
      "crv": "Ed25519",
      "x": "…",
      "kid": "a1b2c3d4e5f60718",
      "alg": "EdDSA",
      "use": "sig"
    }
  ]
}
```

Cache the key set for up to an hour. If you encounter a token with an unknown
`kid` header,
refetch once — that's how key rotation propagates. Keys rotate automatically
roughly every 60 days, and the retiring public key stays in the key set for
24 hours after rotation, so an hourly cache never verifies against a stale set.

## Verifying a Token

Any JWT library that supports EdDSA works. With
`jose` in Node:

```
import { createRemoteJWKSet, jwtVerify } from 'jose'

const JWKS = createRemoteJWKSet(
  new URL('https://shield.edge.network/.well-known/jwks.json')
)

async function verifyOffline(token, sitekey) {
  const { payload } = await jwtVerify(token, JWKS, {
    issuer: 'edge-shield',
    subject: sitekey, // REQUIRED — binds the token to YOUR widget
  })
  return {
    score: payload.score,      // 1–100 humanity score
    hostname: payload.hn,      // hostname the widget ran on
    agent: payload.agt || null // verified agent domain, if declared
  }
}
```

Your verification must check, in order:

- The signature, against a JWKS key matching the token's `kid`
- `iss` equals `edge-shield`
- `sub` equals **your sitekey** — without this check, a token minted for any Shield widget anywhere would pass
- `exp` — tokens live 5 minutes
- Optionally, that `hn` is one of your hostnames

The claims you get back:

```
{
  "iss": "edge-shield",
  "sub": "es_...",              // the widget's sitekey
  "jti": "b3f9…",               // unique token id (replay ledger key)
  "iat": 1785459151,
  "exp": 1785459451,            // 5-minute lifetime
  "score": 92,                  // 1–100 humanity score
  "hn": "example.com",          // hostname the widget ran on
  "agt": "crawler.example",     // optional: verified agent domain
  "agm": "web-bot-auth"         // optional: agent verification method
}
```

## The Trade-Off: No Single-Use Guarantee

The one thing offline verification cannot give you is replay protection. The ledger of
spent tokens lives on Shield's servers, so only
[siteverify](https://edge.network/docs/shield/siteverify)
guarantees that a token verifies exactly once. Verified offline, the same token is valid
for its full 5-minute lifetime, as many times as it's presented.

Use offline verification for high-throughput, lower-stakes endpoints — search,
previews, read APIs — where latency matters more than strict single-use. Keep
siteverify for signups, logins, payments, and anything a farmed token shouldn't touch.

You can narrow the replay window further by rejecting tokens whose
`iat` is older
than your form could plausibly take to submit — 60 seconds is a common choice.

## Next Steps

[Server-Side Validation The strict path, with single-use enforcement](https://edge.network/docs/shield/siteverify) [Testing & CI Fixed test keys that never hit the network's real state](https://edge.network/docs/shield/testing)
[Back to Docs](https://edge.network/docs) [Need help?](https://edge.network/support)
