Don't trust us.
Check it yourself.
A 5arz seal is an ES256 JWT signed by a public key. Fetch that key once, cache it, and every credential after that verifies on your own hardware — no API call, no account, no permission from us.
If we went dark tomorrow, every credential we ever issued would still verify. That isn't generosity. It's the only way the proof means anything.
Verification is public and free. You need an account to create credentials — never to check one.
What "offline" means here
Precision matters, so here is the exact claim.
What is true
You fetch the public key set once over HTTPS and cache it. Every verification after that is a local elliptic-curve signature check. No network call, no round trip, no dependency on 5arz being up, and nothing you do is visible to us.
What is not true
It is not magic. The first key fetch needs the internet, and you should refresh the key set periodically to pick up rotations. A credential also carries an expiry — checking that is your job, and it is one line.
Why this is the whole product
Most verification vendors return a score, and the score is only worth what your trust in the vendor is worth. You cannot audit it, reproduce it, or check it after they are gone. We return a signature instead. A signature is either valid or it isn't, and anyone holding the public key can settle that question without asking anyone's permission — including ours.
Four steps, once
Fetch the key set
GET https://api.5arz.com/.well-known/jwks.json — public, CORS-open, no auth.
Cache it
Keep it in memory, on disk, or committed to your repo. Refresh daily to pick up rotation. Keys are identified by kid, so old and new coexist safely.
Match the header's kid to a key
Decode the JWT header, find the key with that kid. The live signing key today is 5arz-oracle-2.
Verify the ES256 signature and read the claims
Standard P-256 / SHA-256. Every language has it in the standard library or one dependency. Then check exp and read what the credential actually asserts.
Verify one right now
This runs entirely in your browser using the WebCrypto API. Open your network tab — after the key set loads, we make no further requests. Nothing you paste leaves this page.
Key set: not loaded yet.
Don't have a credential handy? The tamper button takes whatever is in the box, flips one character of the payload, and re-verifies — which is the more interesting demonstration anyway. A single changed byte breaks the signature.
The code
Complete and runnable. No 5arz SDK, because there isn't one and you shouldn't need one.
// npm i jose import { createRemoteJWKSet, jwtVerify } from 'jose'; // Fetched once, then cached in-process and refreshed on rotation. const JWKS = createRemoteJWKSet( new URL('https://api.5arz.com/.well-known/jwks.json'), { cacheMaxAge: 86400000 } // 24h ); export async function verifySeal(token) { const { payload, protectedHeader } = await jwtVerify(token, JWKS, { algorithms: ['ES256'], issuer: 'https://5arz.com' }); // Signature is valid and exp/nbf were enforced by jwtVerify. return { ok: true, kid: protectedHeader.kid, claims: payload }; }
# pip install pyjwt[crypto] requests import jwt, requests, time from jwt import PyJWKClient # PyJWKClient caches the key set and only refetches on an unknown kid. _jwks = PyJWKClient("https://api.5arz.com/.well-known/jwks.json", cache_keys=True, lifespan=86400) def verify_seal(token: str) -> dict: key = _jwks.get_signing_key_from_jwt(token).key claims = jwt.decode( token, key, algorithms=["ES256"], issuer="https://5arz.com", ) # raises on bad signature or expiry return claims
// go get github.com/lestrrat-go/jwx/v2 package seal import ( "context" "time" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jwt" ) const jwksURL = "https://api.5arz.com/.well-known/jwks.json" var cache = jwk.NewCache(context.Background()) func Init() { cache.Register(jwksURL, jwk.WithMinRefreshInterval(24*time.Hour)) } func VerifySeal(ctx context.Context, token []byte) (jwt.Token, error) { set, err := cache.Get(ctx, jwksURL) // served from cache if err != nil { return nil, err } return jwt.Parse(token, jwt.WithKeySet(set), jwt.WithValidate(true), jwt.WithIssuer("https://5arz.com"), ) }
# Look at the key set — this is the only network call verification needs. curl -s https://api.5arz.com/.well-known/jwks.json # { # "keys": [{ # "kty": "EC", "crv": "P-256", # "x": "amC_s6qLClaKsDqE5594am8QSc5GP6agTTbjbM_jRdM", # "y": "GYq97U3A7ZuBGJdGEnC_f6dq6HG4MS1RBuKRhJf1T2U", # "kid": "5arz-oracle-2", "alg": "ES256", # "use": "sig", "key_ops": ["verify"] # }] # } # Save it. From here on, verification is local: curl -s https://api.5arz.com/.well-known/jwks.json > jwks.json # Read a credential's claims without verifying (inspection only — # NEVER trust these until the signature checks out): echo "$JWT" | cut -d. -f2 | base64 -d 2>/dev/null | jq .
// No dependencies — WebCrypto is in every modern browser and in // Node 16+ via globalThis.crypto. This is what the widget above runs. const b64u = s => Uint8Array.from( atob(s.replace(/-/g,'+').replace(/_/g,'/') .padEnd(Math.ceil(s.length/4)*4,'=')), c => c.charCodeAt(0)); async function verify(token, jwks) { const [h, p, s] = token.split('.'); const hdr = JSON.parse(new TextDecoder().decode(b64u(h))); const jwk = jwks.keys.find(k => k.kid === hdr.kid); if (!jwk) throw new Error('unknown kid: ' + hdr.kid); // IMPORTANT: strip use/key_ops before importKey. Chrome and Safari // reject a JWK whose key_ops does not exactly match the usages you // request, and our JWKS publishes both fields. This one line is the // difference between working and a DataError. const { use, key_ops, ...clean } = jwk; const key = await crypto.subtle.importKey( 'jwk', clean, { name:'ECDSA', namedCurve:'P-256' }, false, ['verify']); return crypto.subtle.verify( { name:'ECDSA', hash:'SHA-256' }, key, b64u(s), new TextEncoder().encode(h + '.' + p)); }
That use / key_ops strip is the one thing that trips people up. We publish both fields because the JWKS spec allows it; WebCrypto is stricter than the spec. Every other language's library handles it for you.
What the claims mean
A valid signature tells you the credential is authentic and unmodified. It does not tell you what the credential says — that's in the claims, and you should read them.
| Claim | What it asserts |
|---|---|
iss | Who signed it. Should be https://5arz.com — pin this, don't just check the signature. |
iat / exp | When it was issued and when it stops being valid. Enforce exp. |
sub | The subject the proof is about — a session, a task, or a piece of content. |
test | Present and true on sandbox credentials. A test credential is not proof of anything. Reject it in production. |
Two things a signature does not prove
It does not tell you who the person is. A 5arz seal asserts that a real human did a specific thing. It is deliberately not an identity document, and it carries no name, no biometric, and nothing you would have to store carefully.
It does not stop copying. If someone screen-records a sealed video, they have a copy. What they cannot do is produce a valid seal for it — which is what makes the theft provable rather than preventable. We think that distinction is worth being blunt about.
Why not just call an API?
You can — we have one. But here is what changes when verification is local.
| Vendor API check | Offline signature check | |
|---|---|---|
| Works if the vendor is down | No | Yes |
| Works if the vendor is gone | No | Yes |
| Vendor sees what you verify | Yes | No |
| Latency | Network round trip | Sub-millisecond, local |
| Rate limited | Usually | No |
| Costs money at scale | Usually | Free, unlimited |
| Auditable by a third party | No | Yes |
The honest caveat: an offline check cannot know about a revocation issued after your key set was cached. If that matters for your use case, shorten your cache window or call the API. Most uses of a short-lived credential do not need it.
Verifying is free. Always was.
You never needed us to check a seal. You need an account to create them — two fields, no card, no sales call.