Certificates & JWS verification
Every certified translation Verdacert issues — live or sandbox — ships with a compact JWS receipt signed Ed25519. Any party can verify it offline by fetching the public JWKS once. No phone-home, no API key, no centralized trust call.
Why a cryptographic receipt?
The whole point of certified translation is downstream trust. We make that trust independently checkable.
A USCIS reviewer, an opposing lawyer, a university registrar, or anyone else handed a Verdacert-certified PDF can verify the signature without contacting Verdacert. The page that holds their hand is verdacert.com/verify/<id>; the API call backing it is /api/v1/verify/<id>; the cryptographic primitive is a JWS over a deterministic JSON payload, signed Ed25519, verifiable against /.well-known/jwks.json.
Anatomy of a Verdacert certificate
What gets returned by /result and embedded in every PDF QR code.
{
"certificateId": "vcrt_01h…",
"translatorName": "Roya Khan",
"translatorCredentials": "ATA-certified, FA→EN",
"issuedAt": "2026-05-22T18:00:00.000Z",
"publicVerifyUrl": "https://verdacert.com/verify/vcrt_01h…",
"jws": "eyJhbGciOiJFZERTQSIsImtpZCI6Imt1MSJ9.eyJjZXJ0SWQiOiJ2Y3J0XzAxaCJ9.AbCdEf…"
}| Field | Notes |
|---|---|
certificateId | Stable opaque id. Lookup key for the public verify endpoint and the QR-coded link in the PDF. |
translatorName | Legal name of the credentialed reviewer who signed off. |
translatorCredentials | Credentialing body + language pair (e.g. ATA-certified, FA→EN). |
issuedAt | Server-clock UTC ISO 8601. |
publicVerifyUrl | Human-friendly verification URL. Print on the certificate; embed in the PDF QR. |
jws | Compact JWS. Header carries kid (key id) and alg=EdDSA. Payload is a deterministic JSON object. |
JWS payload
The decoded payload (base64url middle segment of the JWS) is a deterministic JSON object with exactly these fields:
{
"certificateId": "vcrt_01h…",
"orderId": "ord_8a3c1b…",
"documentSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4…",
"translatorName": "Roya Khan",
"issuedAt": "2026-05-22T18:00:00.000Z",
"isSandbox": false
}documentSha256binds the certificate to the translated PDF bytes — a verifier who has the file can confirm it's the exact file the cert was issued for. Tamper with the PDF and the hash no longer matches; the cert is now a receipt for a different document.
isSandbox: truein the payload. Your verification code path doesn't branch — your policycode might (e.g. “don't accept sandbox for production filings”). Online verification (the easy path)
One unauthenticated GET. Returns valid/invalid plus the decoded payload and revocation status.
curl https://verdacert.com/api/v1/verify/vcrt_01h…{
"valid": true,
"certificateId": "vcrt_01h…",
"issuedAt": "2026-05-22T18:00:00.000Z",
"revokedAt": null,
"revokedReason": null,
"translator": { "name": "Roya Khan", "credentials": "ATA-certified, FA→EN" },
"documentHash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4…",
"isSandbox": false,
"acceptanceGuarantee": { "uscis": true },
"jws": "eyJhbGciOiJFZERTQSIsImtpZCI6Imt1MSJ9…"
}validcombines two checks: the row isn't revoked, and we re-verified the stored JWS against the active JWKS at request time (defence in depth against the row being tampered with at rest).
Offline verification (zero phone-home)
Recommended for high-volume verifiers and air-gapped contexts. Fetch /.well-known/jwks.json once, cache for ~1 hour, and verify any JWS locally.
The JWKS
curl https://verdacert.com/.well-known/jwks.json{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"alg": "EdDSA",
"kid": "ku1",
"use": "sig",
"x": "…base64url public key bytes…"
}
]
}Keys rotate periodically. The JWS header carries the kid claim so a verifier can pick the right key even mid-rotation.
TypeScript / Node (jose)
npm install joseimport { jwtVerify, createRemoteJWKSet } from "jose";
const jwks = createRemoteJWKSet(
new URL("https://verdacert.com/.well-known/jwks.json"),
{ cooldownDuration: 60 * 60 * 1000 }, // refresh at most hourly
);
export async function verifyVerdacertJws(jws: string) {
const { payload, protectedHeader } = await jwtVerify(jws, jwks, {
algorithms: ["EdDSA"],
});
return { payload, header: protectedHeader };
}
const result = await verifyVerdacertJws("eyJhbGciOiJFZERTQSIsImtpZCI6Imt1MSJ9…");
console.log(result.payload.certificateId, result.payload.documentSha256);Python (PyJWT)
pip install pyjwt[crypto] requestsimport functools, jwt, requests
from jwt import PyJWKClient
jwks_client = PyJWKClient("https://verdacert.com/.well-known/jwks.json", cache_keys=True)
def verify_verdacert_jws(token: str) -> dict:
signing_key = jwks_client.get_signing_key_from_jwt(token)
return jwt.decode(token, signing_key.key, algorithms=["EdDSA"])
payload = verify_verdacert_jws("eyJhbGciOiJFZERTQSIsImtpZCI6Imt1MSJ9…")
print(payload["certificateId"], payload["documentSha256"])Go (golang-jwt + jwk)
import (
"context"
"github.com/golang-jwt/jwt/v5"
"github.com/lestrrat-go/jwx/v2/jwk"
)
var keySet = func() jwk.Set {
s, err := jwk.Fetch(context.Background(), "https://verdacert.com/.well-known/jwks.json")
if err != nil { panic(err) }
return s
}()
func verifyVerdacertJws(tok string) (jwt.MapClaims, error) {
parsed, err := jwt.Parse(tok, func(t *jwt.Token) (any, error) {
kid, _ := t.Header["kid"].(string)
k, ok := keySet.LookupKeyID(kid)
if !ok { return nil, jwt.ErrTokenSignatureInvalid }
var raw any
if err := k.Raw(&raw); err != nil { return nil, err }
return raw, nil
}, jwt.WithValidMethods([]string{"EdDSA"}))
if err != nil { return nil, err }
return parsed.Claims.(jwt.MapClaims), nil
}payload.documentSha256. If the PDF is bundled with a copy of the JWS, this is one hash + one signature verification — milliseconds.Revocation
We retain the right to revoke a certificate (e.g. on a confirmed-fraud finding). Revocation flips revokedAt on the certificate row.
Offline verification can detect revocation in two ways:
- Recommended: hit
/api/v1/verify/<id>for the authoritative answer (DB revocation + live signature). Costs one request, no auth. - Pure offline:verifiers that can't phone home should treat any cert older than their cache window as “unconfirmed” and degrade gracefully. We don't publish a revocation list yet; if you need one, email hello@verdacert.com.
QR codes on the printed certificate
Every certified PDF carries a QR code linking to publicVerifyUrl. A reviewer can scan it from paper with any phone and land on the verification page — no Verdacert account required.
You don't need to do anything to use the QR; it's baked in. If you're embedding the certificate in your own UI, link to publicVerifyUrl alongside any download CTA so end-users have a one-click way to confirm authenticity.
