API documentation

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…"
}
FieldNotes
certificateIdStable opaque id. Lookup key for the public verify endpoint and the QR-coded link in the PDF.
translatorNameLegal name of the credentialed reviewer who signed off.
translatorCredentialsCredentialing body + language pair (e.g. ATA-certified, FA→EN).
issuedAtServer-clock UTC ISO 8601.
publicVerifyUrlHuman-friendly verification URL. Print on the certificate; embed in the PDF QR.
jwsCompact 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.

Sandbox certs are real certificates
Sandbox-issued JWS receipts validate against the same JWKS and have 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 jose
import { 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] requests
import 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
}
Don't forget to check documentSha256
A valid JWS only proves we issued a cert for a document with that hash. To prove the PDF the user is holding is the one we certified, compute its SHA-256 and compare to 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.

Where to next

Get instant quotePricing