Accept BSV micropayments in your Express.js API by seamlessly integrating 402 Payment Required flows with BRC-103 and BRC-104 mutual authentication. This middleware builds upon the Auth middleware—thus letting you identify and authenticate the payer before receiving BSV to monetize your services.
- Monetize your APIs via BSV micropayments.
- Automatically handle
402 Payment Required
logic by providing the amount owed, plus a derivation prefix for the payer to build the transaction. - Integrates seamlessly after the BRC-103 Auth middleware to ensure the user’s identity is established.
- Extensible pricing logic via a user-defined function.
- Background
- Features
- Installation
- Pre-requisites
- Quick Start
- Detailed Usage
- API Reference
- Example Payment Flows
- Security Considerations
- Resources & References
- License
The BRC-103 authentication framework and its BRC-104 HTTP transport extension provide mutual authentication and selective field disclosure. Building on top of these, we can now monetize interactions by requiring micropayments for certain requests. By layering a Payment Middleware after the Auth middleware, your service can signal “402 Payment Required” to the client, prompting them to respond with a BSV transaction that pays for that request.
- Simple 402 Payment Flows: Easily return a
402
status if payment is required. - Configurable Pricing: Provide a
calculateRequestPrice
function to dynamically determine how many satoshis are owed for each request. - Nonce-Based: Uses a derivation prefix to ensure the final payment is bound to your session, preventing replay attacks.
- Auth Integration: Leverages the user’s identity key from the preceding Auth middleware to track who is paying.
- Automatic Transaction Handling: On the server side, calls your
wallet
instance’sinternalizeAction()
to process the transaction.
npm i @bsv/payment-express-middleware
Note: You must also have @bsv/auth-express-middleware
installed and set up before using this payment middleware.
-
BRC-103 / BRC-104–based Auth Middleware
You must install and configure@bsv/auth-express-middleware
first. This ensures every request has a validreq.auth.identityKey
. -
BSV Wallet
A wallet capable of receiving, verifying, and broadcasting transactions. This middleware leverages the standard, bRC-100wallet.internalizeAction()
to handle submitting the payment transaction.- This can be your own custom wallet logic that implements these interfaces.
- The wallet should also be able to verify that the
derivationPrefix
andderivationSuffix
properly correspond to keys in the output script, as per BRC-29. - If you use the wallet implementation from
@bsv/sdk
, these details are handled automatically.
-
Client with 402 Support
On the client side, you need a user agent (e.g., AuthFetch from@bsv/sdk
, or a custom approach) that automatically responds to402
challenges by constructing a BSV transaction to make the payment.
Below is the minimal example integrating the payment middleware with the Auth middleware:
import express from 'express'
import bodyParser from 'body-parser'
import { createAuthMiddleware } from '@bsv/auth-express-middleware'
import { createPaymentMiddleware } from '@bsv/payment-express-middleware'
import { Wallet } from '@your/bsv-wallet'
// 1. Create a BSV wallet that can manage transactions
const wallet = new Wallet({ /* config */ })
// 2. Create the Auth middleware (BRC-103/104)
const authMiddleware = createAuthMiddleware({ wallet })
// 3. Create the Payment middleware
const paymentMiddleware = createPaymentMiddleware({
wallet,
calculateRequestPrice: async (req) => {
// e.g., 50 satoshis per request
return 50
}
})
const app = express()
app.use(bodyParser.json())
// 4. Place Auth middleware first, then Payment middleware
app.use(authMiddleware)
app.use(paymentMiddleware)
// 5. Define your routes as normal
app.post('/somePaidEndpoint', (req, res) => {
// If we got here, the request is authenticated and the payment (if required) was accepted.
res.json({ message: 'Payment received, request authorized', amount: req.payment.satoshisPaid })
})
app.listen(3000, () => {
console.log('Payment-enabled server is listening on port 3000')
})
In this setup:
Auth middleware
ensuresreq.auth
is set.Payment middleware
checks if payment is required (based oncalculateRequestPrice
). If yes, the client must supply a validx-bsv-payment
header with a BSV transaction referencing the specified derivation prefix. Otherwise, the middleware returns a402 Payment Required
response, prompting the client to pay.
import { createPaymentMiddleware } from '@bsv/payment-express-middleware'
const paymentMiddleware = createPaymentMiddleware({
wallet: myWallet,
calculateRequestPrice: (req) => {
// Your logic to return satoshis required for this request
return 100 // e.g. 100 satoshis
}
})
Options:
wallet
(required): A wallet object that can process and broadcast BSV transactions. Must expose aninternalizeAction
method.calculateRequestPrice
(optional): A function(req) => number | Promise<number>
that returns how many satoshis the request should cost. Defaults to100
.
-
Order: Must run after the Auth middleware.
-
Usage:
app.use(authMiddleware) // from @bsv/auth-express-middleware app.use(paymentMiddleware) // from @bsv/payment-express-middleware
-
Effects:
- On each request, it first checks
req.auth.identityKey
. If undefined, returns an error (the Payment middleware requires you to be authenticated). - Determines the price. If
0
, no payment is required—proceeds immediately. - Otherwise, checks the
x-bsv-payment
header from the client. - If the header is missing or invalid, responds with
402 Payment Required
along with thex-bsv-payment-satoshis-required
and a nonce inx-bsv-payment-derivation-prefix
. - If the header is present, tries to finalize the transaction via
wallet.internalizeAction()
. - On success, sets
req.payment
with the transaction details and callsnext()
.
- On each request, it first checks
You can define any logic for calculating the cost of each request, such as:
- A flat fee for all requests (
return 100
) - Per-endpoint pricing
- Different costs based on request size or complexity
- Free requests (return
0
) for certain routes or conditions
const paymentMiddleware = createPaymentMiddleware({
wallet,
calculateRequestPrice: async (req) => {
if (req.path === '/premium') return 500 // cost 500 satoshis
return 0 // free for everything else
}
})
- Authenticated request arrives.
- Payment middleware calls
calculateRequestPrice(req)
. - If
price = 0
, continue. - Else check
x-bsv-payment
header:- If missing → respond with
402 Payment Required
+ nonce (derivation prefix). - If present → parse JSON, verify the nonce, call
wallet.internalizeAction()
.- If successful, sets
req.payment.satoshisPaid = price
. - Continue to your route handler.
- If successful, sets
- If missing → respond with
Returns an Express middleware function that:
- Checks for
req.auth.identityKey
. - Calculates the request’s price.
- Enforces payment via
x-bsv-payment
ifprice > 0
. - On success, attaches
req.payment = { satoshisPaid, accepted, tx }
.
PaymentMiddlewareOptions
:
Property | Type | Required | Description |
---|---|---|---|
calculateRequestPrice |
(req: Request) => number | Promise<number> |
No | Determines how many satoshis are needed to serve the request. Defaults to 100 . |
wallet |
Wallet |
Yes | A wallet instance with a internalizeAction() function to finalize the BSV transaction. |
Once invoked:
- If
price = 0
, setsreq.payment = { satoshisPaid: 0 }
and callsnext()
. - If
price > 0
, requires thex-bsv-payment
header containing a valid BSV transaction (plus a derivation prefix & suffix). - If successful, sets
req.payment = { satoshisPaid: <price>, accepted: true, tx: <transactionData> }
.
const paymentMiddleware = createPaymentMiddleware({
wallet,
calculateRequestPrice: () => 0
})
app.use(authMiddleware)
app.use(paymentMiddleware)
// => All requests are free, effectively ignoring payment logic, but the pipeline remains consistent.
const paymentMiddleware = createPaymentMiddleware({
wallet,
calculateRequestPrice: async (req) => {
// Example: cost is 100 satoshis unless "POST" method, which costs 200
return req.method === 'POST' ? 200 : 100
}
})
When the client tries to call a route, the server may respond with:
{
"status": "error",
"code": "ERR_PAYMENT_REQUIRED",
"satoshisRequired": 200,
"description": "A BSV payment is required to complete this request."
}
along with the header:
x-bsv-payment-satoshis-required: 200
x-bsv-payment-derivation-prefix: <random-nonce-base64>
The client then constructs a BSV transaction paying the appropriate amount to the server’s wallet, referencing the derivation prefix in the transaction metadata. Once complete, the client re-sends the request including:
"x-bsv-payment": JSON.stringify({
derivationPrefix: <same-derivation-prefix>,
derivationSuffix: <some-other-data>,
transaction: <serialized-tx>
})
If accepted, the request proceeds.
-
Run after Auth
This middleware relies onreq.auth.identityKey
from the preceding BRC-103 authentication. If you skip Auth, the identity is unknown, which can break the payment system. -
Nonce Handling
Uses aderivationPrefix
to ensure each payment is unique to the request context. The library verifies the prefix is bound to the server private key.- You should ensure your wallet is robust to replay attacks, e.g., by only accepting each prefix once inside of
internalizeAction()
. - Don't accept the same transaction twice, even if it's still valid! Ensure your wallet throws an error if
internalizeAction()
is called with the same payment multiple times.
- You should ensure your wallet is robust to replay attacks, e.g., by only accepting each prefix once inside of
-
Error Handling
Non-compliant or missingx-bsv-payment
data results in a4xx
error (often402 Payment Required
or400 Bad Request
). -
Transaction Acceptance
The final acceptance or rejection of a transaction is performed by yourwallet.internalizeAction()
. Ensure your wallet’s logic is secure and robust.
- BRC-103 Spec – Mutual authentication & certificate exchange.
- BRC-104 Spec – HTTP transport for BRC-103.
- @bsv/auth-express-middleware – The prerequisite middleware for authentication.
- BRC-29 key derivation protocol – The specification covering
derivationPrefix
andderivationSuffix
as related to the exchange of BSV payments. - 402 Payment Required – The HTTP status code used to signal that payment is required.
Happy Building! If you have questions, run into issues, or want to contribute improvements, feel free to open issues or PRs in our repository.