Concepts

Proof-of-Intent receipts

Every payment captured against a merchant's PayPal order gets a receipt with schema agentguard.proof-of-intent/v1: canonical JSON plus a detached Ed25519 signature with a kid.

Section What it binds
mandate The mandate body and hash, and the user's WebAuthn assertion over that hash (credential id, COSE public key, enrollment provenance). Verifiable without the server.
order The PayPal order snapshot the decision was made on (payee, amount, currency, items) and its digest.
decision Policy outcome, per-rule results, agent id, idempotency key, the proposal hash.
approval For held payments: the passkey-signed approval statement (method: "passkey").
payment Authorization id, capture id and status, amount, times, and AGPOI-<receipt_id> written on the capture as invoice_id.
audit Sequence and hash of the capture entry in the hash-chained audit log, plus the anchor inclusion proof and prior spend of this mandate.
timestamps RFC 3161 token(s) over sha256(canonical(receipt)).
ap2 The same purchase as AP2 mandates.

Getting a receipt

Who Endpoint
Agent GET /v1/agent/payments/:id/receipt or MCP get_payment_receipt
Owner GET /v1/payments/:id/receipt, GET /v1/receipts/:id
Anyone (opt-in) GET /v1/public/receipts/:id when AGENTGUARD_PUBLIC_RECEIPTS=1

The issuer key set is published at /.well-known/agentguard-keys. Pin it.

Verifying

sh
npx -p @agentguard-dev/intent-verifier intent-verify receipt.json --issuer-keys agentguard-keys.json --payee you@shop.example

See Merchant verification for the library API and the live PayPal re-check.

What a receipt is not

It is evidence, not a guaranteed dispute win. PayPal and card networks decide disputes on their own rules, and "item not received" or "not as described" are about fulfilment, which no payment receipt proves.

  • Checked independently by the verifier: the passkey signature over the mandate, the order against the mandate, and for held payments the passkey signature over the approval statement.
  • Attested by the issuer key: the decision, cumulative spend before the first anchor, audit position, enrollment provenance. That is why you pin the key set rather than trusting the key embedded in the receipt. Unknown kids, and receipts signed by a key after its retired_at, are rejected.
  • The passkey proves the holder of that credential signed. Binding it to a real person happens at enrollment.

Edit this page on GitHub