Skip to main content
Gate any API route behind a Lightning payment. No accounts, no subscriptions — clients pay a Lightning invoice and get immediate access. This is an L402-compatible implementation (bLIP-26). Any client that speaks the L402 protocol (HTTP 402 + 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

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.

Cursor

Click to install MCP in Cursor

VS Code

Click to install MCP in VS Code
Claude Code:
After signup, it’s highly recommended to log in at moneydevkit.com and switch to the authenticated MCP server (see the “Existing Account” tab). This connects your agent to your account so it can manage apps, view payments, and access your dashboard.
Option B: Manual setup
  1. Create a moneydevkit account at moneydevkit.com or run npx @moneydevkit/create to generate credentials locally, then grab your api_key and mnemonic.
  2. Install the SDK:
  3. Add environment variables to .env:
  4. Expose the moneydevkit endpoint:
  5. 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 with withPayment to require a Lightning payment:
Every request without a valid token returns a 402 with a Lightning invoice per the L402 protocol. After payment, the same request with the authorization header returns the premium data.

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.
On every authenticated retry, PRODUCTS-mode endpoints re-resolve the product and look for a price whose (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:
The pricing function is evaluated both when creating the invoice and when verifying the token. If the price changes between issuance and verification (e.g., the client replays a cheap token on an expensive tier), the request is rejected with amount_mismatch.

Fiat pricing

Use currency: '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 with expirySeconds:

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:
  1. JSON body fieldsandbox: true appears alongside the standard fields.
  2. WWW-Authenticate parameter — the L402 challenge includes sandbox="true".
  3. 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).
Example sandbox 402 response:
The invoice is not payable — and the BOLT11 description literally instructs decoders not to try. AI agents should detect any of the three signals and skip the payment attempt entirely. In sandbox mode, preimage verification on subsequent requests is skipped, so the client can submit any 64-character hex preimage (e.g., "0".repeat(64)) and retry the same endpoint to proceed past the 402.

Client integration

Any HTTP client can consume an L402 endpoint.

curl

The 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:
If your handler returns without calling 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: booleantrue means the client should discard the credential and request a fresh 402 (price changed, product/price retired).
  • phase: 'create' | 'verify' — present on pricing_error to indicate whether the failure occurred at 402 issuance (create) or during an authenticated retry (verify). Useful for merchants grepping logs by failure point.