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

ClaimMeaning
receiptIdUnique id for this access.
issuer / issuerKeyIdWho signed it, and with which key.
principalId / operatorIdThe verified agent (and its operator, when known).
resourceId / resourceVersionExactly what was accessed.
action / purposeThe declared, signature-bound action and purpose.
policyId / policyVersion / ruleIdThe policy decision that granted access.
obligationsTerms the agent accepted (e.g. attribution: required).
entitlement{ provider, reference }, e.g. x402 plus the settled transaction reference, or an API-key grant reference.
contentDigestDigest binding the receipt to the served content, when set.
issuedAt / expiresAtValidity window (default lifetime 3600 s).

Issuing

createAgentAccess issues receipts automatically, you just configure the signing key:

ts
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:

ts
import { signReceipt } from "@corri/sdk/server";
const jws = await signReceipt(payload, { issuerKey: RECEIPTS_PRIVATE_JWK });

Verifying

ts
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.

Try it live: run the paid demo scenario, the response panel decodes the receipt's claims and verifies its signature against the published demo public key, server-side.