This source candidate declares SDK peer ^2.1.6 || ^3.0.0. SDK3 remains
a coordinated proposal; see the qualification and migration limits
before adopting it.
BRC-121 HTTP 402 Payment Required handler for client and server. Client-side: auto-pays 402 responses. Server-side: middleware/validation for accepting BSV micropayments over HTTP.
npm install @bsv/402-pay// ===== SERVER SIDE =====
import express from 'express'
import { createPaymentMiddleware } from '@bsv/402-pay/server'
const app = express()
app.use(
'/articles/:slug',
createPaymentMiddleware({
wallet,
replayStore: sharedAtomicReplayStore,
calculatePrice: path => {
if (path.includes('/premium/')) return 1000
return undefined
}
})
)
app.get('/articles/:slug', (req, res) => {
if (req.payment) {
res.json({ article: 'Paid content here', paidBy: req.payment.senderIdentityKey })
} else {
res.json({ article: 'Free content' })
}
})
// ===== CLIENT SIDE =====
import { create402Fetch } from '@bsv/402-pay/client'
const fetch402 = create402Fetch({
wallet,
cacheTimeoutMs: 30 * 60 * 1000
})
const response = await fetch402('https://example.com/articles/foo')
const article = await response.json()
console.log(article)satoshisPaid, senderIdentityKey, and txidimport express from 'express'
import { validatePayment, send402 } from '@bsv/402-pay/server'
const app = express()
app.get('/premium', async (req, res) => {
const price = 100
const result = await validatePayment(req, wallet, price)
if (!result || !result.accepted) {
send402(res, serverIdentityKey, price)
return
}
res.json({
content: 'Premium stuff',
tx: result.txid,
paidBy: result.senderIdentityKey
})
})import { constructPaymentHeaders } from '@bsv/402-pay/client'
const headers = await constructPaymentHeaders(
wallet,
'https://example.com/articles/foo',
100, // 100 sats
serverPublicKey
)
const res = await fetch('https://example.com/articles/foo', { headers })const fetch402 = create402Fetch({
wallet,
cacheTimeoutMs: 30 * 60 * 1000 // Cache for 30 minutes
})
// Make requests — auto-pays 402s with caching
const response = await fetch402('https://api.example.com/content')
const data = await response.json()
// Clear cache between sessions
fetch402.clearCache()x-bsv-sats, x-bsv-server for payment requestOP_DUP OP_HASH160 <hash> OP_EQUALVERIFY OP_CHECKSIG@bsv/payment-express-middleware with @bsv/auth-express-middlewarex-bsv-time is >30 seconds old, payment is rejectedisMerge detail is defense in depth, not the public replay contractcreate402Fetch requires working WalletClientPaymentReplayStore into every serving processThe middleware is silent by default and uses fixed bounded wallet descriptions.
Supply its optional structured logger only when the deployment can protect
transaction diagnostics. If wallet or replay-store processing fails after
internalization starts, the middleware returns 503 without a new payment
challenge so a client is not encouraged to spend twice. satoshisPaid reports
the actual selected output value, including any overpayment.