Skip to content

Latest commit

 

History

History
100 lines (74 loc) · 2.75 KB

File metadata and controls

100 lines (74 loc) · 2.75 KB

Quickstart: API Provider

Add signed receipts to your Express.js API in under 5 minutes. Every response will include a PEAC-Receipt header with a verifiable JWS.

Prerequisites

  • Node.js >= 22.0.0
  • An existing Express.js application (or create one below)

1. Install

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

2. Generate a signing key

import { generateKeypair, exportJWK } from '@peac/crypto';

const { publicKey, privateKey } = await generateKeypair();
const jwk = await exportJWK(publicKey, privateKey);
console.log(JSON.stringify(jwk, null, 2));
// Save this JWK securely. Share only the public key.

3. Add the middleware

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

const app = express();

app.use(
  peacMiddleware({
    issuer: 'https://api.example.com',
    signingKey: {
      kty: 'OKP',
      crv: 'Ed25519',
      x: '<base64url public key from step 2>',
      d: '<base64url private key from step 2>',
    },
    keyId: 'prod-2026-03',
  })
);

app.get('/api/data', (req, res) => {
  res.json({ message: 'Hello World' });
  // PEAC-Receipt header is automatically added to the response
});

app.listen(3000, () => console.log('Server running on port 3000'));

4. Verify it works

curl -i http://localhost:3000/api/data

You should see a PEAC-Receipt header in the response containing a compact JWS.

5. Verify the receipt offline

In a separate script or on the consumer side, verify the receipt using the issuer's public key:

import { verifyLocal } from '@peac/protocol';
import { importPublicKey } from '@peac/crypto';

// The issuer's public key (from the JWK saved in step 2, public part only)
const publicKey = await importPublicKey({
  kty: 'OKP',
  crv: 'Ed25519',
  x: '<base64url public key from step 2>',
});

const receiptJws = '<the PEAC-Receipt header value from the curl response>';
const result = await verifyLocal(receiptJws, publicKey);
console.log('Valid:', result.valid);
if (result.valid) {
  console.log('Issuer:', result.claims.iss);
  console.log('Kind:', result.claims.kind);
}

What you get

  • Every API response carries a signed receipt
  • Receipts verify offline with just the public key
  • No network calls needed for verification
  • Receipts survive across organizational boundaries

Next steps