Verify a receipt
Paste any VerityLayer verdict receipt below. We check its Ed25519 signature against our public key and tell you whether it's genuinely ours, unaltered. We've pre-filled a live signed receipt so you can try it in one click — then change a single character and watch it fail.
Under the hood this is a free POST https://api.veritylayer.dev/receipt/verify — the same check anyone, anywhere can run against our public key at /.well-known/verity-pubkey.json. No account. No payment. Prefer the terminal? curl https://api.veritylayer.dev/receipt/selftest
Or verify it without us entirely
The box above is convenient, but be clear about what it is: it POSTs to
/receipt/verify, which is the issuer vouching for its own signature.
If that is the only way to check a receipt, you are still trusting us. So it isn't.
Two standalone verifiers ship in the open-source repo. They import nothing from VerityLayer, make no network call at verification time, and deliberately share no code with the service that signs — if they agreed because they were the same code, that would prove nothing.
# fetch the key once, then check receipts forever, offline curl -o pubkey.json https://api.veritylayer.dev/.well-known/verity-pubkey.json python verify_receipt.py receipt.json pubkey.json # stdlib + cryptography node verify_receipt.js receipt.json pubkey.json # zero dependencies
The exact bytes that get signed are specified in RECEIPTS.md, including the two places it deliberately diverges from RFC 8785 — named, rather than left for you to hit.
A receipt that can refuse
Checking a signature and calling that “verified” is the mistake. A valid signature proves we signed something. It never proves the receipt permits what you are about to do. Every row below is a real signature that must not open a gate:
| A genuine receipt that… | why it must still be refused |
|---|---|
| is about a different claim | you verified the wrong thing |
says unsupported | we checked, and it was false |
says uncertain | we checked, and we do not know |
| is six weeks old | true then; the world moved |
is a test self-test | proves signing works, vouches for nothing |
| already authorized an action | replayed |
require_receipt refuses all six, in Python and TypeScript, with identical
behaviour verified against a shared cross-language fixture:
pip install "verity-guard[offline]" npm install @veritylayer/guard require_receipt(receipt, PUBKEY, claim="Invoice 4417 is unpaid") send_payment() # unreachable unless the receipt vouches for THAT claim
Strict by default — only supported passes, receipts older
than 15 minutes are stale, self-tests are refused — and loosening any of it is
explicit, so it always shows up in a diff. This is the difference between a receipt you
can check and a receipt that can stop you: an agent cannot claim it verified something it
did not.