Receipt verifier
Verify a receipt yourself.
Paste a receipt. Your browser recomputes its hash and its checks root, checks the signature against our published key, and asks our server one question: is this hash in the ledger? The receipt holds no agent output, so there is nothing sensitive to paste.
Also prove which text it is about (optional)
A receipt commits to the output with a salted hash. If you hold the original output and the receipt_salt returned with it, the commitment can be checked here — locally, nothing is uploaded.
Paste a receipt or load the sample.
- Structure
Required fields are present and the version is supported.
- Hash
The SHA-256 of the canonical body equals
hash. - Checks root
Rebuilt from the public ruleset and the listed findings.
- Signature
Ed25519 over the hash, against the key we publish.
- Ledger anchor
The hash is recorded in the issuer's append-only ledger.
- Commitment
Optional: the receipt is about the text you supplied.
How it works
Four checks you can repeat.
01 · hash
The body is its own fingerprint
Remove hash and signature, serialise what is left as canonical JSON (RFC 8785) and take the SHA-256. One changed character gives a different result.
02 · checks root
The findings have to add up
The ruleset is public. Every rule not listed in findings passed, so the root over all results can be rebuilt by anyone — a removed finding breaks it.
03 · signature
Only the issuer could sign it
Receipts from the API carry an Ed25519 signature over the hash. The public key is at /.well-known/agentgate-keys.json.
04 · anchor
It sits in a chain
Each receipt names the one before it. The ledger lookup confirms this hash holds a place in that sequence — so a receipt cannot be quietly dropped later.
Verify offline, in a dozen lines
You do not need this page. The format is documented and the checks use nothing beyond SHA-256 and Ed25519, both in every standard library.
import { createHash, createPublicKey, verify } from "node:crypto"; // RFC 8785 canonical JSON: sorted keys, no whitespace const canon = (v) => Array.isArray(v) ? `[${v.map(canon)}]` : v && typeof v === "object" ? `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${canon(v[k])}`)}}` : JSON.stringify(v); const { hash, signature, ...body } = receipt; const digest = "sha256:" + createHash("sha256").update(canon(body)).digest("hex"); console.log("hash ok:", digest === hash); const jwks = await (await fetch("https://agengate.com/.well-known/agentgate-keys.json")).json(); const key = createPublicKey({ key: jwks.keys.find((k) => k.kid === signature.kid), format: "jwk" }); console.log("signature ok:", verify(null, Buffer.from(hash), key, Buffer.from(signature.sig, "base64url")));