---
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.

## 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)
