AttestPage · Docs · Pricing · OpenAPI · llms.txt

Demo: one paid call and its signed receipt

Last updated: 2026-10-10

One call to POST /v1/verify/quote, recorded in full: the request, the 402 payment offer, the paid retry with x402, the answer with its signed receipt, and how to check the receipt's signature yourself with nothing but Node.js.

The example is signed with a demo key, published in step 5, so every check on this page works offline. Receipts from https://vehcdj664efetfrsolne5umanq.srv.us are signed with the keys at https://still-rapids-9yt7.here.now/.well-known/jwks.json and are checked the same way.

1. The request

An agent wants to quote a page and sends the URL and its exact words:

POST /v1/verify/quote HTTP/1.1
Host: attestpage.example
Content-Type: application/json

{
  "url": "https://www.iana.org/help/example-domains",
  "quote": "These domains may be used as illustrative examples in documents"
}

2. The 402 offer

No payment came with the request, so the answer is 402 Payment Required and nothing is charged. The PAYMENT-REQUIRED header is base64 JSON:

HTTP/1.1 402 Payment Required
PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6Miwi…

{
  "error": {
    "code": "payment_required",
    "message": "This route costs US$0.01 via x402 (see the PAYMENT-REQUIRED header and /pricing.json)."
  }
}

Decoded, without the route description and the extensions:

{
  "x402Version": 2,
  "error": "Payment required",
  "resource": {
    "url": "https://attestpage.example/v1/verify/quote"
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:84532",
      "amount": "10000",
      "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
      "payTo": "0x08dE2e056F6BE74162c400fF877449593579D1EA",
      "maxTimeoutSeconds": 120,
      "extra": {
        "name": "USDC",
        "version": "2"
      }
    }
  ]
}

amount is in USDC base units (6 decimals), so 10000 is US$0.01, on Base Sepolia (eip155:84532, a test network). Prices for every route: pricing.json.

3. The paid retry

The agent's x402 client signs a USDC transfer authorization for exactly that amount and sends the same request again with a PAYMENT-SIGNATURE header. Decoded, without the extensions and with the signature shortened:

{
  "x402Version": 2,
  "resource": {
    "url": "https://attestpage.example/v1/verify/quote"
  },
  "accepted": {
    "scheme": "exact",
    "network": "eip155:84532",
    "amount": "10000",
    "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    "payTo": "0x08dE2e056F6BE74162c400fF877449593579D1EA",
    "maxTimeoutSeconds": 120,
    "extra": {
      "name": "USDC",
      "version": "2"
    }
  },
  "payload": {
    "authorization": {
      "from": "0x94310DbA0d34ec79FE88E7c361FEd4d97Ba253D3",
      "to": "0x08dE2e056F6BE74162c400fF877449593579D1EA",
      "value": "10000",
      "validAfter": "0",
      "validBefore": "1791641445",
      "nonce": "0xf6a01de3ed3452c574551ca36e5da1be0a049a2dd8fef948e005363869e3df31"
    },
    "signature": "0x85d7070591…0a1b"
  }
}

Any x402 v2 client does this step. One x402 payment step by step shows it with plain fetch, and the MCP server does it for you.

4. The answer and its receipt

The check runs and the payment is settled: 200 OK, with a PAYMENT-RESPONSE header. Decoded, without the transaction hash and the extensions:

{
  "success": true,
  "payer": "0x94310DbA0d34ec79FE88E7c361FEd4d97Ba253D3",
  "network": "eip155:84532",
  "amount": "10000"
}

The body:

{
  "result": {
    "match": "exact",
    "score": 1,
    "start": 171,
    "end": 234,
    "context": "...e.com and example.org are maintained for documentation purposes. These domains may be used as illustrative examples in documents without prior coordination with us. They are not available for re...",
    "occurrences": 1,
    "normalised_sha256": "75a93b6d9b17a11bb2277dde1de0e57ac357967052d4ae3e4d660856ff22e81d"
  },
  "page": {
    "requested_url": "https://www.iana.org/help/example-domains",
    "final_url": "https://www.iana.org/help/example-domains",
    "http_status": 200,
    "content_type": "text/html",
    "content_kind": "real",
    "signals": [
      "status_200"
    ],
    "title": "Example Domains",
    "redirects": [],
    "content_sha256": "6a7cca043c62d5d13f7d4c6f2eba2839c6446c06b5c7be6cb29d77ad6ab80e46",
    "raw_sha256": "eeb94115fe65c771f01a0cc0701cded276a75cab3c0bb71cbf34daf802ce4a44",
    "retrieved_at": "2026-10-10T14:08:45.771Z",
    "server_ip": "104.18.24.232",
    "tls": {
      "cert_sha256": "cbf3a200a42e2b6b751fb6beb6d1b72a464a0ab7ffebfca0fff071af2c185b64",
      "issuer": "C=US, O=Google Trust Services, CN=WE1",
      "subject": "CN=www.iana.org",
      "valid_from": "2026-08-21T20:51:32.000Z",
      "valid_to": "2026-11-19T21:51:30.000Z"
    },
    "headers": {
      "date": "Sat, 10 Oct 2026 14:08:45 GMT",
      "last-modified": "Sat, 10 Oct 2026 13:40:39 GMT",
      "content-type": "text/html; charset=utf-8",
      "content-length": "1762"
    },
    "bytes": 1762,
    "truncated": false
  },
  "injection_flags": [],
  "advice": "The page loaded and looks like real content.",
  "receipt": "eyJhbGciOiJFZERTQSIsImtpZCI6Imh0dHBzOi8vYXR0ZXN0cGFnZS5leGFtcGxlLy53ZWxsLWtub3duL2p3a3MuanNvbiNhcC1XZDRDU1FwNzFFR2hUU1RtIiwidHlwIjoiYXR0ZXN0cGFnZS1ldmlkZW5jZStqd3MifQ.eyJjb250ZW50X2tpbmQiOiJyZWFsIiwiY29udGVudF9zaGEyNTYiOiI2YTdjY2EwNDNjNjJkNWQxM2Y3ZDRjNmYyZWJhMjgzOWM2NDQ2YzA2YjVjN2JlNmNiMjlkNzdhZDZhYjgwZTQ2IiwiZmluYWxfdXJsIjoiaHR0cHM6Ly93d3cuaWFuYS5vcmcvaGVscC9leGFtcGxlLWRvbWFpbnMiLCJoZWFkZXJzIjp7ImNvbnRlbnQtbGVuZ3RoIjoiMTc2MiIsImNvbnRlbnQtdHlwZSI6InRleHQvaHRtbDsgY2hhcnNldD11dGYtOCIsImRhdGUiOiJTYXQsIDEwIE9jdCAyMDI2IDE0OjA4OjQ1IEdNVCIsImxhc3QtbW9kaWZpZWQiOiJTYXQsIDEwIE9jdCAyMDI2IDEzOjQwOjM5IEdNVCJ9LCJodHRwX3N0YXR1cyI6MjAwLCJpYXQiOjE3OTE2NDEzMjUsImlzcyI6Imh0dHBzOi8vYXR0ZXN0cGFnZS5leGFtcGxlIiwia2luZCI6InF1b3RlIiwicGF5bWVudCI6eyJhbW91bnQiOiIxMDAwMCIsImFzc2V0IjoiMHgwMzZDYkQ1Mzg0MmM1NDI2NjM0ZTc5Mjk1NDFlQzIzMThmM2RDRjdlIiwibmV0d29yayI6ImVpcDE1NTo4NDUzMiIsInBheWVyIjoiMHg5NDMxMERiQTBkMzRlYzc5RkU4OEU3YzM2MUZFZDRkOTdCYTI1M0QzIiwicmFpbCI6Ing0MDIifSwicXVvdGVfc2hhMjU2IjoiZGYzNGJlMGZjZWFiNTZmMjY5NmRkMDgxNjk0YjVjOTI0Y2JkZGE1MjA2NDdhMThjZWJjMDFmNWQ3ZTIyMzY0ZiIsInJhd19zaGEyNTYiOiJlZWI5NDExNWZlNjVjNzcxZjAxYTBjYzA3MDFjZGVkMjc2YTc1Y2FiM2MwYmI3MWNiZjM0ZGFmODAyY2U0YTQ0IiwicmVxdWVzdF9zaGEyNTYiOiI5MTM1N2I3MDE0YzE1ZDcyYjIxOGVkMmE1NWY3YjM4ZTg1NmRmMjZmMTk4N2ViNjhlYTg5MjVhODkxNmI5MWVhIiwicmVzdWx0Ijp7ImNhc2Vfc2Vuc2l0aXZlIjpmYWxzZSwiZW5kIjoyMzQsIm1hdGNoIjoiZXhhY3QiLCJtb2RlIjoiZnV6enkiLCJub3JtYWxpc2VkX3NoYTI1NiI6Ijc1YTkzYjZkOWIxN2ExMWJiMjI3N2RkZTFkZTBlNTdhYzM1Nzk2NzA1MmQ0YWUzZTRkNjYwODU2ZmYyMmU4MWQiLCJvY2N1cnJlbmNlcyI6MSwic2NvcmUiOjEsInN0YXJ0IjoxNzF9LCJyZXRyaWV2ZWRfYXQiOiIyMDI2LTEwLTEwVDE0OjA4OjQ1Ljc3MVoiLCJzZXJ2ZXJfaXAiOiIxMDQuMTguMjQuMjMyIiwidGllciI6InBhaWQiLCJ0bHMiOnsiY2VydF9zaGEyNTYiOiJjYmYzYTIwMGE0MmUyYjZiNzUxZmI2YmViNmQxYjcyYTQ2NGEwYWI3ZmZlYmZjYTBmZmYwNzFhZjJjMTg1YjY0IiwiaXNzdWVyIjoiQz1VUywgTz1Hb29nbGUgVHJ1c3QgU2VydmljZXMsIENOPVdFMSIsInN1YmplY3QiOiJDTj13d3cuaWFuYS5vcmciLCJ2YWxpZF9mcm9tIjoiMjAyNi0wOC0yMVQyMDo1MTozMi4wMDBaIiwidmFsaWRfdG8iOiIyMDI2LTExLTE5VDIxOjUxOjMwLjAwMFoifSwidXJsIjoiaHR0cHM6Ly93d3cuaWFuYS5vcmcvaGVscC9leGFtcGxlLWRvbWFpbnMiLCJ2IjoxfQ.8-fl77yE3sNKc3N6fxl66PqRc_suw221_4GXBVxHxmGB4bn2cwPZuqgeLetjjxTMGCSU4rqOhy3vbI3xd5UqBw",
  "tier": "paid",
  "rail": "x402",
  "network": "eip155:84532"
}

result.match is exact: the quoted words are on the page, at characters 171 to 234 of its text. page.content_kind is real: the page loaded as content, not a bot wall or an error page. receipt is a compact JWS (header.payload.signature, each base64url) that signs the URL, the time, the page and quote hashes, the match and the payment. Field meanings: How it works.

5. Check the receipt signature

The receipt's header names the signing key:

{
  "alg": "EdDSA",
  "kid": "https://attestpage.example/.well-known/jwks.json#ap-Wd4CSQp71EGhTSTm",
  "typ": "attestpage-evidence+jws"
}

The key is published in the issuer's JWKS at /.well-known/jwks.json. For this example it is:

{
  "keys": [
    {
      "kty": "OKP",
      "crv": "Ed25519",
      "x": "C8UR8Mb1iCu0SutA6j0Zn5yG965ifppI3A0kVqcsy-M",
      "kid": "https://attestpage.example/.well-known/jwks.json#ap-Wd4CSQp71EGhTSTm",
      "alg": "EdDSA",
      "use": "sig"
    }
  ]
}

Save the receipt string as receipt.txt and the key set as jwks.json. This script, check.mjs, uses only Node.js built-ins. It finds the key whose kid matches the header and verifies the Ed25519 signature over header.payload:

import crypto from 'node:crypto';
import fs from 'node:fs';

const jws = fs.readFileSync(process.argv[2], 'utf8').trim();
const { keys } = JSON.parse(fs.readFileSync(process.argv[3], 'utf8'));
const [h, p, s] = jws.split('.');
const header = JSON.parse(Buffer.from(h, 'base64url'));
const jwk = keys.find((k) => k.kid === header.kid);
if (header.alg !== 'EdDSA' || !jwk) throw new Error('no matching key');
const key = crypto.createPublicKey({ key: { kty: jwk.kty, crv: jwk.crv, x: jwk.x }, format: 'jwk' });
const ok = crypto.verify(null, Buffer.from(`${h}.${p}`), key, Buffer.from(s, 'base64url'));
if (!ok) {
  console.log('bad signature');
  process.exit(1);
}
const claims = JSON.parse(Buffer.from(p, 'base64url'));
console.log('valid', claims.iss, claims.kind, claims.result.match, claims.tier);
node check.mjs receipt.txt jwks.json
valid https://attestpage.example quote exact paid

The last word is the receipt's tier: a paid receipt ends in paid, a receipt from a free-trial call in trial, and a free sample in sample.

Change one character in the middle of the receipt's payload (the part between the two dots) and it prints bad signature. Then check that iss is the issuer you expect: a valid signature from a key you did not choose means nothing.

Check your own receipts

For a receipt from https://vehcdj664efetfrsolne5umanq.srv.us, use the issuer's keys:

curl -s https://still-rapids-9yt7.here.now/.well-known/jwks.json > jwks.json
node check.mjs receipt.txt jwks.json

Or send it to the free route POST /v1/receipt/verify (Verify a receipt), or use our single-file checker, which also checks the issuer and the receipt fields: Verify receipts offline. To make the call yourself, step by step with curl: How to check a quote an LLM cites.