Paid-test receipts
A grade is a summary. A receipt is the evidence behind it.
Every time Lumière PayCheck pays an x402 endpoint to test it, we record a receipt and sign it with our published Ed25519 key. Anyone can check a receipt offline, compare it with the endpoint's live payment terms, and (once that test wallet is retired) find the payment on-chain. Receipts started on November 7, 2026; tests before that have no receipt.
Where to find them
- On every endpoint page: the Recent paid tests table has a Signed link for each test with a receipt.
- API (free, no key):
GET /v1/test-receipts?url=<endpoint>returns the newest receipts for one endpoint (add&limit=up to 100, or&id=for one receipt). - MCP: the
paid_test_receiptstool returns the same thing to agents.
What a receipt says
The signed payload:
| Field | Meaning |
|---|---|
id | Receipt id (rcpt_…) |
url | The endpoint we paid |
network, asset | Chain and token, exactly as quoted |
amountQuoted | The price in the endpoint's 402 quote (atomic units; USDC has 6 decimals) |
amountPaid | What was actually collected (0 when the endpoint didn't settle the payment) |
payTo | The payout wallet we paid |
termsHash | Hash of the exact payment terms we paid (below) |
responseHash, responseBytes | Hash and size of the response body we got back |
httpStatus | The HTTP status of the paid response |
outcome | The test result: delivered, suspect (first failure, re-test scheduled), bad_response (confirmed failure), payment_failed, auth_required, input_required, example_not_found, mismatch or error (neutral) |
testedAt | When the test ran (UTC) |
commitment | A hash that commits to our test wallet and the settlement transaction (below) |
Outside the signature, each receipt in the API also says whether the seller later refunded a failed test, and, for retired test wallets, the revealed payer, transaction and salt.
How each hash is made
All hashes are SHA-256, hex.
- termsHash = sha256 of
JSON.stringify(["x402-terms.v1", url, scheme, network, asset, amount, payTo]), with the values exactly as they appeared in the endpoint's 402 quote. Request the endpoint without paying, build the same string from its quote, and compare: the same hash means the terms haven't changed since our test. - responseHash = sha256 of the response body we received, as UTF-8 text. Sellers can compare it with their own logs to confirm exactly what they sent us.
- commitment = sha256 of
JSON.stringify(["paycheck-commit.v1", payer, tx, salt]).
Why the payer and transaction come later
If sellers knew which wallet is our tester, they could give it a perfect answer and real buyers something worse, and paid tests would stop meaning anything. So while a test wallet is in use, a receipt only carries a commitment to the payer and the transaction.
When we retire a test wallet, every receipt it signed shows revealed: { payer, tx, salt }. Recompute the commitment from those three values: if it matches the signed commitment, the receipt was about that exact payment, and you can look the transaction up on Basescan or Solscan.
Checking the signature
A receipt is <body>.<signature>, both base64url. body is the base64url-encoded JSON payload. The signature is Ed25519 over the ASCII bytes of paycheck-test-receipt.v1. followed by body. (The prefix means a test receipt can never be mistaken for one of our payment-approval receipts, which use the same key.)
The public key is in our JWKS at /.well-known/jwks.json, kid: "paycheck-receipts-1".
The quick way: POST /v1/test-receipts/verify with {"receipt": "<receipt>"}. Add "payer", "tx" and "salt" to also check a revealed commitment.
In Node.js, with no dependencies:
import { createPublicKey, verify, createHash } from "node:crypto";
const jwks = await (await fetch("https://lumierepaycheck.org/.well-known/jwks.json")).json();
const key = createPublicKey({ key: jwks.keys.find(k => k.kid === "paycheck-receipts-1"), format: "jwk" });
const [body, sig] = receipt.split(".");
const ok = verify(null, Buffer.from("paycheck-test-receipt.v1." + body, "ascii"), key, Buffer.from(sig, "base64url"));
const payload = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
// Once revealed:
const sha = s => createHash("sha256").update(s).digest("hex");
const matches = sha(JSON.stringify(["paycheck-commit.v1", payer, tx, salt])) === payload.commitment;What a receipt proves, and what it doesn't
- It proves what PayCheck recorded about a test, at the time, and that we can't change it afterwards without the signature breaking.
- With the live 402, anyone can confirm the terms we paid were the endpoint's real terms.
- With the seller's logs, the seller can confirm the response we graded is the one they sent.
- After the reveal, anyone can confirm on-chain that the payment happened, from which wallet, for how much.
- It doesn't publish the response itself. Paid content belongs to the seller, so only its hash is public.