Skip to main content
Version: v0.10.13

Express Middleware

Add automatic PEAC receipt issuance to your Express.js application. Every HTTP response gets a cryptographically signed PEAC-Receipt header with zero per-route boilerplate.

One middleware, every response gets a signed PEAC-Receipt header

Drop peacMiddleware() into your Express app and every outgoing response automatically carries a verifiable receipt. No per-route code required.


Install

Terminal
pnpm add @peac/protocol @peac/middleware-express

Basic setup

server.ts
import express from 'express';
import { peacMiddleware } from '@peac/middleware-express';

const app = express();

app.use(
peacMiddleware({
privateKey: process.env.PEAC_PRIVATE_KEY,
kid: 'peac-2026-02',
issuer: 'https://api.example.com',
})
);

app.get('/api/data', (req, res) => {
res.json({ message: 'This response has a PEAC receipt' });
});

app.listen(3000);

Every response now includes a signed receipt:

Response Headers
HTTP/1.1 200 OK
Content-Type: application/json
PEAC-Receipt: eyJhbGciOiJFZERTQSIsInR5cCI6...

Configuration

Required options

OptionTypeDescription
privateKeystringEd25519 private key (base64url or hex encoded)
kidstringKey ID -- must match the kid in your published JWKS
issuerstringYour service URL (becomes the iss claim)

Optional options

OptionTypeDefaultDescription
audiencestring--Default audience for all receipts
attestationTypestring'interaction'Default attestation type
includeRequestHashbooleanfalseHash the request body into the receipt payload
skipPathsstring[][]Paths to skip (e.g., ['/health', '/metrics'])
onError(err: Error) => voidlog and continueCustom error handler

Route-specific configuration

Override the global middleware options on individual routes by applying peacMiddleware() directly:

payment-route.ts
app.post(
'/api/payment',
peacMiddleware({
privateKey: process.env.PEAC_PRIVATE_KEY,
kid: 'peac-2026-02',
issuer: 'https://api.example.com',
attestationType: 'payment',
}),
paymentHandler
);

Route-level middleware overrides the global instance for that route only. All other routes continue using the global configuration.


With payment evidence

Attach payment evidence from a rail adapter to enrich the receipt with commerce data:

checkout.ts
import { fromStripePaymentIntent } from '@peac/rails-stripe';

app.post('/api/checkout', async (req, res) => {
const paymentIntent = await stripe.paymentIntents.create({ ... });
const evidence = fromStripePaymentIntent(paymentIntent);

// Attach evidence to the response for the middleware
res.locals.peacEvidence = evidence;
res.json({ success: true });
});

The middleware detects res.locals.peacEvidence and folds it into the signed receipt automatically.


Framework-agnostic core

Building middleware for other frameworks?

The @peac/middleware-core package provides the framework-agnostic middleware logic. Use it to build middleware for Fastify, Koa, Hono, or any other Node.js framework.

custom-adapter.ts
import { createMiddlewareCore } from '@peac/middleware-core';

const core = createMiddlewareCore({
privateKey: process.env.PEAC_PRIVATE_KEY,
kid: 'peac-2026-02',
issuer: 'https://api.example.com',
});

// Use core.issueForRequest(request) in your framework adapter

Next steps