lazysite supports the x402 HTTP payment protocol. Pages can require
payment before being served. When an unpaid request arrives, the
processor returns 402 Payment Required with an X-Payment-Response
header describing payment terms. An x402-compatible wallet or browser
extension reads this header, makes the on-chain payment, and retries
the request with proof.
Payment proof validation is delegated to an upstream proxy. The
processor trusts an X-Payment-Verified header, the same pattern
as X-Remote-User for authentication.
---
title: Premium Article
payment: required
payment_amount: 0.01
payment_currency: USD
payment_network: base
payment_address: 0x1234...your-wallet-address
payment_description: Access to premium article
---
payment: requiredrequired.
payment_amount0.01 for one cent USD).
payment_currencyUSD.
payment_networkbase, ethereum, polygon.
payment_addresspayment_assetpayment_descriptionMembers in specified groups skip payment entirely. Add auth_groups:
to the payment-gated page:
---
title: Members Content
payment: required
payment_amount: 0.01
payment_currency: USD
payment_network: base
payment_address: 0x1234...
auth_groups:
- members
- sponsors
---
Authenticated users in members or sponsors see the content
without paying. All other users (including unauthenticated) see the
402 payment page.
Create 402.md in the docroot. These TT variables are available:
[% payment_amount %] - amount from front matter[% payment_currency %] - currency from front matter[% payment_network %] - network from front matter[% payment_address %] - wallet address[% payment_description %] - description textIf 402.md does not exist, a minimal built-in page is shown.
After payment is verified:
[% payment_paid %] - 1 if payment proof verified[% payment_payer %] - payer wallet address if available[% payment_bypassed %] - 1 if access granted via group membership[% payment_amount %] - amount from front matter[% payment_currency %] - currency from front matter[% payment_address %] - wallet addressThe X-Payment-Response header returned with 402 responses contains
JSON:
{
"version": "1.0",
"accepts": [{
"scheme": "exact",
"network": "base",
"maxAmountRequired": "10000",
"to": "0x1234...",
"asset": "0x...",
"extra": {
"name": "USDC",
"version": "1"
}
}]
}
maxAmountRequired is in the asset's smallest unit (USDC has 6
decimals, so 0.01 USD = 10000).
In production, configure a payment proxy to:
X-Payment-Proof headerX-Payment-Verified: 1 and optionally X-Payment-PayerConfigure header names in lazysite/lazysite.conf:
payment_header_verified: X-Payment-Verified
payment_header_payer: X-Payment-Payer
payment-demo.pl simulates payment via signed cookies for
testing. Not for production use.
Simulate payment:
/cgi-bin/payment-demo.pl?action=pay&page=/premium&amount=0.01
Clear payment:
/cgi-bin/payment-demo.pl?action=unpay&page=/premium
Demo payments expire after 1 hour.
The dev server auto-detects the demo script and routes requests through it when present.
Payment-gated pages are never cached to disk and always include
Cache-Control: no-store, private. This prevents paid content from
being served without payment verification.