# Verify receipts offline

Last updated: 2026-10-10

You do not need to trust our server to check a receipt. A single-file checker (Node.js 20 or later, no dependencies) verifies the Ed25519 signature and the payload against our public keys.

## Get the checker

```sh
curl -O https://vehcdj664efetfrsolne5umanq.srv.us/dl/verify-receipt.mjs
node verify-receipt.mjs --selftest
```

[https://vehcdj664efetfrsolne5umanq.srv.us/dl/index.json](https://vehcdj664efetfrsolne5umanq.srv.us/dl/index.json) lists the checker (`verify-receipt`) with its URL, size and sha256, so you can check the file you downloaded.

## Check a receipt

```sh
node verify-receipt.mjs '<receipt>' --issuer https://still-rapids-9yt7.here.now
```

This fetches `https://still-rapids-9yt7.here.now/.well-known/jwks.json` and checks that the receipt's `iss` and key id belong to that issuer. The issuer is a fixed address that does not change when the service moves, so a receipt keeps verifying after the service address changes. Always pin `--issuer` (or a key file you trust): without it, the key location comes from the receipt itself.

To work fully offline, save the keys once and pass the file:

```sh
curl -o jwks.json https://still-rapids-9yt7.here.now/.well-known/jwks.json
node verify-receipt.mjs '<receipt>' --jwks jwks.json --issuer https://still-rapids-9yt7.here.now
```

Pass `-` instead of the receipt to read it from standard input.

## Output

JSON `{valid, reason?, kid, tier, payload}`. Exit code 0 means valid, 1 invalid, 2 a usage error.

## Doing it yourself

A receipt is a compact JWS: `header.payload.signature`, each base64url. Check that the header has `alg` `EdDSA` and `typ` `attestpage-evidence+jws`, find the key in the JWKS whose `kid` matches the header's `kid`, and verify the Ed25519 signature over `header.payload`. Then check that `iss` is the issuer you expect. Field meanings are in [How it works](/docs/how-it-works).
