L402 auth scheme) can interact with your API out of the box. The legacy LSAT scheme is also accepted for backwards compatibility.
How it works
1
Client requests a protected endpoint without credentials
2
Server returns 402 with a Lightning invoice and a signed token
3
Client pays the invoice and receives a preimage (proof of payment)
4
Client retries with Authorization: L402 <token>:<preimage>
5
Server verifies the token, expiry, and preimage — then forwards to the handler
Setup
- Next.js
- Replit / Express
Option A: Use an AI coding assistant (recommended)Install the MCP server and let your AI agent handle setup. When the agent asks for your email, use a real email address so you can log in to your dashboard later.Claude Code:Option B: Manual setup
Cursor
Click to install MCP in Cursor
VS Code
Click to install MCP in VS Code
- Create a moneydevkit account at moneydevkit.com or run
npx @moneydevkit/createto generate credentials locally, then grab yourapi_keyand mnemonic. - Install the SDK:
- Add environment variables to
.env: - Expose the moneydevkit endpoint:
- Configure Next.js:
If you’ve already set up moneydevkit for checkouts, you can skip the steps above — L402 uses the same
MDK_ACCESS_TOKEN and MDK_MNEMONIC.Basic usage
Wrap any route handler withwithPayment to require a Lightning payment:
- Next.js
- Replit / Express
PaymentConfig
withPayment accepts the same shape as the dashboard SDK’s createCheckout from @moneydevkit/core — a single discriminated union that picks AMOUNT or PRODUCTS mode based on the type field. The L402 wrapper adds two things: every field accepts a (req: Request) => value resolver for per-request dynamic pricing/metadata, and an expirySeconds field controls the credential + invoice lifetime.
(amount, currency) matches the credential’s frozen values. If no matching price is found (the price was retired or replaced), the request is rejected with amount_mismatch (403, recoverable) — the agent should request a fresh 402 challenge. If the product fetch fails (e.g., the product was deleted), the request fails with pricing_error (500, phase: 'verify').
Dynamic pricing
Pass a function instead of a fixed number to compute the price from the request:amount_mismatch.
Fiat pricing
Usecurrency: 'USD' to price in US cents. The SDK converts to sats at the current exchange rate when generating the invoice:
Token expiry
Tokens and their invoices expire after 15 minutes by default. Override withexpirySeconds:
Sandbox responses
When a merchant’s app is running in sandbox mode (e.g. Replit preview), the 402 response signals sandbox state on three independent channels so any client implementation can detect it:- JSON body field —
sandbox: trueappears alongside the standard fields. - WWW-Authenticate parameter — the L402 challenge includes
sandbox="true". - BOLT11 description tag — the invoice’s description is set to the literal string
you_are_in_sandbox_mode_do_not_attempt_payment_just_pass_a_fake_preimage(the whole description, not a prefix).
"0".repeat(64)) and retry the same endpoint to proceed past the 402.
Client integration
Any HTTP client can consume an L402 endpoint.curl
WWW-Authenticate header follows the bLIP-26 format:
Programmatic (Node.js / AI agent)
Deferred settlement
By default,withPayment marks the credential as used immediately before your handler runs. If your handler fails after the credential is consumed, the payer can’t retry.
Use withDeferredSettlement when the service delivery might fail and you want the payer to be able to retry. Your handler receives a settle() callback - call it only after you’ve successfully delivered the service:
- Next.js
- Replit / Express
settle() (e.g. it throws or the service fails), the credential stays valid and the payer can retry with the same macaroon and preimage.
settle() is callable only once per request. A second call returns { settled: false, error: 'already_settled' } without hitting the backend.
A 402 is only returned when no L402/LSAT authorization header is present. If the header is present but malformed or invalid, you get a 401 - not a new invoice. This prevents wasting invoices on bad auth attempts.
Error codes
Error envelope extensions
Error responses optionally include two fields beyond{ code, message, details }:
recoverable: boolean—truemeans the client should discard the credential and request a fresh 402 (price changed, product/price retired).phase: 'create' | 'verify'— present onpricing_errorto indicate whether the failure occurred at 402 issuance (create) or during an authenticated retry (verify). Useful for merchants grepping logs by failure point.

