Access receipts
Every successful access produces a signed, portable receipt: compact JWS (RFC 7515), alg EdDSA (Ed25519), header typ: "corri-receipt+jws". Receipts verify offline given the issuer's public key.
Receipts turn "the agent accessed the content" into a verifiable artifact both sides can keep: publishers get an audit trail of who accessed what under which terms; agents get proof they paid and complied. The receipt travels in the Access-Receipt response header and is handed to your protected handler as context.receipt.
Claims
| Claim | Meaning |
|---|---|
receiptId | Unique id for this access. |
issuer / issuerKeyId | Who signed it, and with which key. |
principalId / operatorId | The verified agent (and its operator, when known). |
resourceId / resourceVersion | Exactly what was accessed. |
action / purpose | The declared, signature-bound action and purpose. |
policyId / policyVersion / ruleId | The policy decision that granted access. |
obligations | Terms the agent accepted (e.g. attribution: required). |
entitlement | { provider, reference }, e.g. x402 plus the settled transaction reference, or an API-key grant reference. |
contentDigest | Digest binding the receipt to the served content, when set. |
issuedAt / expiresAt | Validity window (default lifetime 3600 s). |
Issuing
createAgentAccess issues receipts automatically, you just configure the signing key:
const access = createAgentAccess({
// …
receipts: {
issuerKey: RECEIPTS_PRIVATE_JWK, // JWK, PEM, or CryptoKey
issuerKeyId: "receipts-2026", // recorded in the JWS header (default "receipts-1")
expiresInSeconds: 3600 // default 3600
}
});
The low-level signer is exported too, for issuing receipts outside the pipeline:
import { signReceipt } from "@corri/sdk/server";
const jws = await signReceipt(payload, { issuerKey: RECEIPTS_PRIVATE_JWK });
Verifying
import { verifyReceipt, decodeReceipt } from "@corri/sdk";
const v = await verifyReceipt(receiptString, {
// Called with the receipt's issuer + issuerKeyId; return the public key.
resolveIssuerKey: (issuer, keyId) =>
issuer === "https://demo.corri.dev" && keyId === "demo-receipts-2026"
? DEMO_RECEIPTS_PUBLIC_JWK
: null
});
if (v.valid) {
v.payload.resourceId; // "report:grid-brief"
v.payload.entitlement; // { provider: "x402", reference: "tx:…" }
} else {
v.reason; // why it failed
}
// Reading without verifying, never trust this alone:
const claims = decodeReceipt(receiptString);
Verification checks the EdDSA signature, the corri-receipt+jws type header, the payload structure, and the validity window. Because everything needed is in the token plus one public key, a receipt can be verified by a third party with no call back to the issuer.