Every GeoClear API response can carry an X-GeoClear-Receipt HTTP header. The header is a JWS (RFC 7515) in compact serialization, base64url(header).base64url(payload).base64url(signature).
This guide walks through what's inside the receipt, how it's signed, and how a verifier checks it.
The receipt payload
The signed payload is a canonicalized JSON object containing the verdict + framing fields:
iss, issuer; alwayshttps://geoclear.ioiat, issued-at timestamp (millisecond precision)kid, key identifier; rotates annually with a 30-day JWKS overlap windowalg, signing algorithm;ES384(ECDSA over P-384)aud, audience (the requester's API key id, where applicable)payload_hash, SHA-256 of the canonical response body- The verdict itself, the response fields
The signing path
Signing happens inside an HSM. The private key never lives in application code. GeoClear's application servers do not have direct access to the signing material; only signing operations bound to specific request contexts pass through.
The signing key is rotated annually. Both the new key and the previous key are published in the JWKS for a 30-day overlap window so receipts issued before rotation continue to verify cleanly.
The JWKS endpoint
The public verification material is published at /.well-known/jwks.json. Every entry includes the kid, kty, crv, x, and y needed to verify ES384 signatures.
The JWKS is a static file, hashed and CDN-cached, served with the same uptime profile as the rest of the site. Customers can mirror or archive the JWKS for long-term verification.
Verification flow
- Read the
X-GeoClear-Receiptheader. - Parse the JWS protected header to read
kid,alg. - Fetch the JWKS (or use a cached / archived copy).
- Find the JWK entry whose
kidmatches the receipt header. - Verify the signature against the JWS protected header + payload using
ES384. - Re-canonicalize the response body using the same canonicalization rules and compute SHA-256.
- Compare the recomputed hash to the receipt's
payload_hash. - If both the signature and the hash check out, the receipt is valid.
Verification options
- Browser, with WebCrypto. The verifier at /security#receipt-demo runs the full flow in your browser using
crypto.subtle.verify. After the verifier and JWKS are loaded, signature verification runs locally, GeoClear's application servers do not participate in the verification result. - Server-side, with
@geoclear/verify-receipt. The npm package wraps the verification in a single function. Open-source (MIT), zero GeoClear-specific runtime dependencies. - Server-side, with any standard JOSE library. The receipt is plain JWS. Any RFC-7515-compliant verifier works, node-jose, jose, python-jose, go-jose, etc.
Long-term verification
Receipts remain verifiable when customers retain the receipt, the canonical payload, the key ID, and the public key material associated with the receipt's kid. The JWKS publishes every historical signing key with its activation/retirement window, when a review at audit time asks about a verdict from a past period, the verifier finds the right key entry by kid and runs the same verification recipe.
This is the trust boundary: the receipt does not depend on a vendor dashboard or support ticket to be valid. It is a self-contained cryptographic artifact verifiable against retained material.
Try it
, Shailesh, founder at GeoClear
More: All resources · Architecture whitepaper · Why agents need signed verdicts