@zbdpay/agent-pay is the server half of the L402 protocol. It creates invoice-backed payment challenges and verifies payment proof before allowing access.
Install
npm install @zbdpay/agent-pay
Environment
| Variable | Required | Default |
|---|
ZBD_API_KEY | Yes (unless passed in config) | none |
ZBD_API_BASE_URL | No | https://api.zbdpay.com |
Quickstarts
import express from "express";
import { createExpressPaymentMiddleware } from "@zbdpay/agent-pay";
const app = express();
app.get(
"/protected",
createExpressPaymentMiddleware({
amount: 21,
apiKey: process.env.ZBD_API_KEY,
}),
(_req, res) => {
res.json({ ok: true });
},
);
import { Hono } from "hono";
import { createHonoPaymentMiddleware } from "@zbdpay/agent-pay";
const app = new Hono();
app.use(
"/protected",
createHonoPaymentMiddleware({
amount: 21,
apiKey: process.env.ZBD_API_KEY,
}),
);
import { withPaymentRequired } from "@zbdpay/agent-pay/next";
export const GET = withPaymentRequired(
{
amount: 21,
apiKey: process.env.ZBD_API_KEY,
},
async () => Response.json({ ok: true }),
);
PaymentConfig
type PaymentConfig<RequestLike = unknown> = {
amount: number | ((request: RequestLike) => number | Promise<number>);
currency?: "SAT" | "USD";
apiKey?: string;
tokenStorePath?: string;
};
SAT pricing is the default and the most direct production path.
402 Response Shape
WWW-Authenticate: L402 macaroon="<token>", invoice="<bolt11>"
{
"error": {
"code": "payment_required",
"message": "Payment required"
},
"macaroon": "<token>",
"invoice": "<bolt11>",
"paymentHash": "<hash>",
"amountSats": 21,
"expiresAt": 1735766400
}
Error Codes
| HTTP | Code | Meaning |
|---|
| 402 | payment_required | No valid payment proof provided |
| 401 | invalid_credential | Macaroon signature invalid |
| 401 | invalid_payment_proof | Preimage mismatch |
| 403 | resource_mismatch | Proof for different route |
| 403 | amount_mismatch | Proof for different price |
| 403 | token_expired | Proof expired |
| 500 | configuration_error | Missing API key/config |
| 500 | pricing_error | Dynamic pricing function failed |
| 502 | invoice_creation_failed | Upstream invoice creation failed |
Runnable Example
The repository includes examples/http-server.mjs for a minimal paid route.
From the agents workspace, run:
npm --prefix agent-pay run build
ZBD_API_KEY=<your_api_key> npm --prefix agent-pay run example:http-server
Optional verbose logs:
ZBD_PAY_DEBUG=1 ZBD_API_KEY=<your_api_key> npm --prefix agent-pay run example:http-server
Then hit the route with your wallet CLI:
zbdw fetch "https://api.example.com/protected" --max-sats 100
Storage
By default, settled token metadata is persisted at:
~/.zbd-wallet/server-tokens.json
Override via tokenStorePath when needed for your deployment model.