# How to check a signed receipt before you rely on it, step by step with curl

Last updated: 2026-10-13

To check that a receipt really came from AttestPage and was not altered, send it to `POST /v1/receipt/verify`. It is free: no payment, account or key. The route checks the signature against our published keys and returns `valid`, the key that signed it, the `tier` and the decoded payload.

This guide checks a sample receipt with `curl` and `jq` against `https://vehcdj664efetfrsolne5umanq.srv.us`, then shows the same call as an MCP tool. For every field and error, see [Verify a receipt](/docs/receipt-verify). To check without calling our server at all, see [Verify a receipt offline](/docs/verify-offline).

## What the check does

- **Checks the signature.** The receipt is a compact JWS signed with Ed25519. The route looks up the signing key in our [JWKS](https://vehcdj664efetfrsolne5umanq.srv.us/.well-known/jwks.json) and checks the signature and the issuer.
- **Returns the payload.** On success you get the decoded fields, such as `kind`, `url`, `content_sha256` and `retrieved_at`.
- **Names a reason when it fails.** `valid` is `false` and `reason` is one of `malformed`, `bad_alg`, `bad_signature`, `unknown_kid`, `bad_iss` or `tier_key_mismatch`.
- **Says what kind of receipt it is.** `tier` is `paid`, `trial` or `sample`. Sample receipts use a separate key and say nothing about a third-party page.
- **Says when the key is retired.** A receipt signed by a key we have since retired still checks as `valid`, and the answer adds `retired: true`. The signature is still good; the key is no longer used to sign new receipts.
- **Stores nothing.** The receipt is checked and dropped.

A valid receipt records what our fetcher saw at the time it was signed. It does not say the page was correct.

## Price

Free. `POST /v1/receipt/verify` is not charged, needs no x402 payment and has no trial limit. Paid routes that issue receipts are listed in [pricing.json](https://vehcdj664efetfrsolne5umanq.srv.us/pricing.json); they are paid in USDC with x402 v2 on Base mainnet (real USDC).

## 1. Get a sample receipt

The free sample fetch returns a receipt signed with the sample key. Save it as `receipt.txt`:

```sh
curl -s https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/fetch | jq -r .receipt > receipt.txt
```

A receipt from a paid or trial call works the same way: take the `receipt` field of the answer.

## 2. Check it

```sh
jq -Rs '{receipt: rtrimstr("\n")}' receipt.txt \
  | curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/receipt/verify -H 'content-type: application/json' -d @- \
  | jq '{valid, tier, kind: .payload.kind, url: .payload.url}'
```

```json
{
  "valid": true,
  "tier": "sample",
  "kind": "fetch",
  "url": "https://vehcdj664efetfrsolne5umanq.srv.us/v1/sample/page"
}
```

The body is `{"receipt": "<compact JWS>"}`, at most 163,840 characters. A body that is not JSON, or a missing or too long `receipt`, gets 400.

## 3. See what an altered receipt looks like

Change the last four characters of the receipt and check it again:

```sh
r=$(cat receipt.txt)
jq -n --arg r "${r%????}AAAA" '{receipt: $r}' \
  | curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/receipt/verify -H 'content-type: application/json' -d @- \
  | jq '{valid, reason, message}'
```

```json
{
  "valid": false,
  "reason": "bad_signature",
  "message": "Signature does not verify."
}
```

Treat any receipt with `valid: false` as unusable. The route still answers 200, so read `valid` and do not rely on the status code.

## 4. Read the payload

When `valid` is `true`, `payload` has the facts the receipt records. For a fetch receipt:

```sh
jq -Rs '{receipt: rtrimstr("\n")}' receipt.txt \
  | curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/v1/receipt/verify -H 'content-type: application/json' -d @- \
  | jq '.payload | {kind, iss, retrieved_at, content_kind, http_status}'
```

`iss` is the issuer, `https://still-rapids-9yt7.here.now`. A receipt whose issuer differs fails with `bad_iss`. To match page text to the receipt, hash the text with SHA-256 and compare it with `content_sha256`.

## With MCP

The same check is the free MCP tool `attestpage_verify_receipt`, with `{"receipt": "<compact JWS>"}` as its arguments. To use it from an MCP client with nothing to install, add the remote endpoint:

```json
{
  "mcpServers": {
    "attestpage": { "type": "http", "url": "https://vehcdj664efetfrsolne5umanq.srv.us/mcp" }
  }
}
```

One call to the remote endpoint with `curl`, using the receipt from step 1:

```sh
jq -Rs '{jsonrpc: "2.0", id: 1, method: "tools/call", params: {name: "attestpage_verify_receipt", arguments: {receipt: rtrimstr("\n")}}}' receipt.txt \
  | curl -s -X POST https://vehcdj664efetfrsolne5umanq.srv.us/mcp -H 'content-type: application/json' -d @- \
  | jq '.result.content[0].text | fromjson | {valid, tier}'
```

```json
{
  "valid": true,
  "tier": "sample"
}
```

## Questions

### How much does it cost to check a receipt?

Nothing. `POST /v1/receipt/verify` and the MCP tool `attestpage_verify_receipt` are free, with no payment and no trial limit.

### Does a valid receipt mean the page is accurate?

No. It means we signed it and it has not changed since. It records what our fetcher saw at the time, not whether the page was right.

### Can I check a receipt without calling your server?

Yes. The signing keys are public in the [JWKS](https://vehcdj664efetfrsolne5umanq.srv.us/.well-known/jwks.json), so any Ed25519 and JWS library can check a receipt. [Verify a receipt offline](/docs/verify-offline) shows how.
