# Signed receipts

> Every response to an authenticated agent carries an Ed25519-signed AgentGate-Receipt. Verify it offline with the JWKS or online.

Every response to an authenticated agent (Web Bot Auth or the shared key,
on the agent API and for verified browser agents on the form) carries a
signed receipt of the decision:

```http
AgentGate-Receipt: eyJhbGciOiJFZERTQSIsInR5cCI6ImFnZW50Z2F0ZS1yZWNlaXB0K2p3dCIsImtpZCI6ImUxIn0.eyJpc3MiOi…
```

A receipt is signed when the status is written, so it records what the
agent actually got (for example `402` or `429`). A dropped request gets no
receipt.

## Format

A compact JWS:

| Part | Content |
| --- | --- |
| Header | `alg` `EdDSA`, `typ` `agentgate-receipt+jwt`, `kid` |
| `iss` | the issuing AgentGate |
| `sub` | your agent |
| `jti` | the decision ID |
| `iat` | issue time |
| `site`, `action`, `route` | where and what |
| `outcome` | `allow`, `challenge`, `block`, `payment_required`, `rate_limited`, `conflict` or `error` |
| `status` | the HTTP status you received |

A JWS was chosen over an RFC 9421 response signature because a receipt
must outlive the response it describes.

## Verify

- **Offline.** Fetch `/.well-known/agentgate-keys.json` (a JWKS of OKP
  Ed25519 keys) and use any JOSE library that supports EdDSA. Check `typ`.
  In Go, `VerifyReceipt(token, ParseJWKS(jwks))` is the reference verifier.
- **Online.** `POST /v1/receipts/verify` with `{"receipt": "…"}` returns
  `valid`, the claims, and whether the decision is still in the site's log
  and matches (`recorded`, `matches`). The log is kept for 7 days by
  default.

Keys stay in the JWKS after a rotation until the operator retires them. Keep
a copy of the JWKS alongside any receipts you may need to prove later.
