Outcome: Your HTTP API emits signed receipts on every response so consumers can verify what terms applied and what happened — offline, with just your public key.
Audience: API provider.
Time: About 5 minutes from a clean clone.
An API operator wants to add portable proof to every response. Consumers may be paying partners, downstream services, auditors, or agents acting on behalf of users. Local logs are not enough — the other party needs a signed record that survives your log retention and can be checked without calling back to your service.
PEAC issues a compact JWS on every response, carried in the PEAC-Receipt HTTP header. The signature lets anyone with your public key verify offline.
PEAC packages:
@peac/middleware-express— Express middleware that issues records on each response.@peac/protocol— issuance and offline verification.@peac/crypto— Ed25519 signing.
Optional adjacent systems: any HTTP server (Express shown; Hono / Koa / Fastify adapters exist). Any signing-key custody option (in-process key, KMS, HSM) works as long as it implements the signing callback.
Prerequisites: Node 22+, pnpm 8+, Express.
-
Install dependencies:
pnpm add @peac/middleware-express @peac/protocol @peac/crypto
-
Create an Ed25519 keypair and publish the public key at
/.well-known/peac-issuer.json+ JWKS (/.well-known/jwks.json). A minimal harness:import { generateKeypair } from '@peac/crypto'; const { privateKey, publicKey, kid } = await generateKeypair();
-
Wire the middleware into your Express app:
import express from 'express'; import { peacIssue } from '@peac/middleware-express'; const app = express(); app.use( peacIssue({ issuer: 'https://api.example.com', privateKey, kid, // Map each response into a claim payload. claimsFromResponse: (req, res) => ({ kind: 'evidence', type: 'org.peacprotocol/api-receipt', pillars: ['access'], ext: { access: { path: req.path, method: req.method, status: res.statusCode, }, }, }), }) ); app.get('/api/v1/resource', (req, res) => { res.json({ ok: true }); });
-
Inspect a response:
curl -i https://api.example.com/api/v1/resource # ... # PEAC-Receipt: eyJhbGciOiJFZERTQSIsInR5cCI6ImludGVyYWN0aW9uLXJlY29yZCtqd3QifQ... # Link: </.well-known/peac-issuer.json>; rel="issuer"
-
Verify the record offline from any consumer:
import { verifyLocal } from '@peac/protocol'; const result = await verifyLocal(receiptHeader, publicKey, { issuer: 'https://api.example.com', }); console.log(result.valid, result.claims.type, result.claims.ext.access);
A decoded record payload for an authorized GET looks like this:
{
"iss": "https://api.example.com",
"iat": 1781609600,
"jti": "019676d0-0000-7000-8000-000000000000",
"kind": "evidence",
"type": "org.peacprotocol/api-receipt",
"pillars": ["access"],
"peac_version": "0.2",
"schema": "interaction-record+jwt",
"ext": {
"access": {
"path": "/api/v1/resource",
"method": "GET",
"status": 200
}
}
}The JOSE header carries typ: interaction-record+jwt, alg: EdDSA, and kid for the signing key. The HTTP response body remains whatever your handler returned; the receipt is additive on the response.
pnpm install && pnpm build
pnpm --filter @peac/middleware-express test
pnpm --filter @peac/example-hello-world demoThe @peac/middleware-express test suite exercises the issue-on-response path; examples/hello-world issues a record and verifies it offline in the same script.
- API Provider Quickstart — the five-minute walkthrough with full source.
packages/middleware-express/— middleware reference.docs/specs/PROTOCOL-BEHAVIOR.md— normative issuance and verification behavior.docs/compatibility/COMPATIBILITY_MATRIX.md— Adapter Readiness for@peac/middleware-expressand@peac/protocol.