BRC-121 HTTP 402 Payment Required

BRC-121 monetizes HTTP API endpoints with a single-round-trip payment flow: client requests a resource, server responds with 402 and payment instructions (a nonce), client derives a payment address, constructs a BSV transaction, and re-sends the original request with payment headers. Server validates and serves the resource (or rejects with another 402).

At a glance

FieldValue
FormatOpenAPI 3.1
Version1.0.0
Statusstable
Implementations@bsv/402-pay, @bsv/payment-express-middleware

What problem this solves

Monetize APIs without subscriptions or token systems. Traditional APIs require accounts, credit cards, or API keys. BRC-121 enables instant monetization: each request is micro-paid in satoshis. No signup, no account needed—just pay per request and immediate access.

Replay-proof payment derivation. The server provides a fresh nonce for each 402 response. The client must use that exact nonce to derive the payment address. This prevents replay attacks: a payment for one request cannot be reused for another.

Stateless server operation. The server doesn't maintain client sessions or payment history. Every request is independent. The payment derivation is deterministic (nonce + timestamp), so the server can validate payments without database lookups.

Protocol overview

Single round-trip with payment:

  1. Client → Server GET /protected-resource (no payment headers)

    • Server checks if resource is free (price = 0) → serve it
    • Otherwise, server sends 402 Payment Required
  2. Server → Client HTTP 402 response

    • x-bsv-server: <serverPublicKey> — Server's identity key
    • x-bsv-sats: <amount> — Satoshis required
  3. Client (constructs payment)

    • Generates a fresh random nonce and millisecond timestamp
    • Calls wallet.createAction() with one P2PKH output
    • Derives address using key ID <nonce> <base64(timestamp)>
    • Uses BRC-42 to derive (pubKeyA, pubKeyB) from sender+server keys
    • Locks the output to the derived server payment key
    • Signs and constructs Atomic BEEF (BRC-95)
  4. Client → Server same request with payment headers

    • x-bsv-beef: <beefBase64> — Atomic BEEF transaction
    • x-bsv-sender: <clientPublicKey> — Sender's identity key
    • x-bsv-nonce: <nonce> — Client-generated derivation prefix
    • x-bsv-time: <timestamp> — Unix millisecond timestamp
    • x-bsv-vout: <outputIndex> — Which output contains the payment
  5. Server (validates & serves)

    • Calls wallet.internalizeAction() with BEEF
    • If accepted === true → payment accepted, serve resource (HTTP 200)
    • If the transaction, nonce, amount, or output index is invalid → reject (HTTP 402)
    • If amount < required → reject (HTTP 402)

Key types / endpoints

MethodPathPurposeRequestResponse
GET/POST/etc./{resourcePath}First request (unpaid)(none)402 + x-bsv-sats, x-bsv-server
GET/POST/etc./{resourcePath}Second request with paymentx-bsv-beef, x-bsv-sender, x-bsv-nonce, x-bsv-time, x-bsv-vout200 + resource body, or 402 on failure

Example: Monetized Express endpoint

typescript
import express from 'express'
import { createPaymentMiddleware } from '@bsv/402-pay/server'
import { ServerWallet } from '@bsv/simple/server'

const wallet = await ServerWallet.create({
  privateKey: process.env.SERVER_PRIVATE_KEY!,
  network: 'main',
  storageUrl: 'https://store-us-1.bsvb.tech'
})
const app = express()

app.use(createPaymentMiddleware({
  wallet,
  calculatePrice: (path) => {
    // Pricing logic
    if (path === '/premium') return 500  // 500 sats
    if (path === '/free') return 0       // Free
    return 100  // Default 100 sats
  }
}))

// Protected endpoint
app.get('/premium', (req, res) => {
  // If we reach here, payment was accepted
  res.json({
    content: 'Premium article here',
    paidBy: req.payment.senderIdentityKey,
    amount: req.payment.satoshisPaid,
    txid: req.payment.txid
  })
})

app.listen(3000)

Client-side (using create402Fetch):

typescript
import { create402Fetch } from '@bsv/402-pay/client'
import { WalletClient } from '@bsv/sdk'

const wallet = new WalletClient()

// Fetch wrapper that auto-handles 402
const fetch402 = create402Fetch({ wallet })

const response = await fetch402('https://api.example.com/premium')
const data = await response.json()
console.log(data.content)  // Automatic payment + resource retrieval

Conformance vectors

There is no standalone BRC-121 vector directory in the current conformance corpus. Related portable fixtures live in conformance/vectors/wallet/brc29/payment-derivation.json for payment derivation and conformance/vectors/wallet/brc100/ for wallet methods used by payment clients and servers.

Implementations in ts-stack

PackageNotes
@bsv/402-pay/serverServer-side middleware for independent 402 handling without BRC-31 auth
@bsv/402-pay/clientClient-side fetch wrapper that auto-detects 402 and constructs payment headers
@bsv/payment-express-middlewareExpress middleware for payment gating that requires auth middleware first and uses x-bsv-payment JSON

Spec artifact

brc121.yaml