L402 Lightning payment middleware for .NET — paywall any API in one line. Built on ln.bot.
Two NuGet packages:
LnBot.L402— Client-side. Auto-pay L402-protected APIs with anyHttpClient. Works in console apps, background services, MAUI — anything withHttpClient.LnBot.L402.AspNetCore— Server-side. Protect ASP.NET Core routes behind L402 paywalls with middleware,[L402]attributes, or endpoint filters.
Both packages are thin glue layers. All L402 logic — macaroon creation, signature verification, preimage checking — lives in the ln.bot API via the LnBot SDK. Zero crypto dependencies.
L402 is a protocol built on HTTP 402 Payment Required. It enables machine-to-machine micropayments over the Lightning Network:
- Client requests a protected resource
- Server returns
402with a Lightning invoice and a macaroon token - Client pays the invoice, obtains the preimage as proof of payment
- Client retries the request with
Authorization: L402 <macaroon>:<preimage> - Server verifies the token and grants access
L402 is ideal for API monetization, AI agent tool access, pay-per-request data feeds, and any scenario where you want instant, permissionless, per-request payments without subscriptions or API key provisioning.
dotnet add package LnBot.L402.AspNetCore # Server (includes client package)
dotnet add package LnBot.L402 # Client only (no ASP.NET Core dependency)using LnBot;
using LnBot.L402;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton(new LnBotClient("key_..."));
var app = builder.Build();
app.UseL402Paywall("/api/premium", new L402Options
{
Price = 10,
Description = "API access",
});
app.MapGet("/api/premium/data", (HttpContext context) =>
{
var l402 = context.GetL402();
return Results.Ok(new { data = "premium content", paymentHash = l402?.PaymentHash });
});
app.MapGet("/api/free/health", () => Results.Ok(new { status = "ok" }));
app.Run();[ApiController]
[Route("api/[controller]")]
public class WeatherController : ControllerBase
{
[L402(Price = 50, Description = "Weather forecast", ExpirySeconds = 3600, Caveats = new[] { "tier=pro" })]
[HttpGet("forecast")]
public IActionResult GetForecast()
=> Ok(new { forecast = "sunny" });
}app.MapGet("/api/premium/data", () => Results.Ok(new { data = "premium" }))
.AddEndpointFilter(new L402EndpointFilter(price: 10, description: "API access"));app.UseL402Paywall("/api/dynamic", new L402Options
{
PriceFactory = context =>
{
if (context.Request.Path.StartsWithSegments("/api/dynamic/bulk"))
return Task.FromResult(50);
return Task.FromResult(5);
}
});| Option | Type | Description |
|---|---|---|
Price |
int |
Price in satoshis per request |
PriceFactory |
Func<HttpContext, Task<int>>? |
Dynamic pricing callback (takes precedence over Price) |
Description |
string? |
Invoice memo shown in wallets |
ExpirySeconds |
int? |
Challenge expiry in seconds |
Caveats |
List<string>? |
Macaroon caveats to attach |
After a successful paywall check, use the GetL402() extension method to access the verification result:
app.MapGet("/api/premium/data", (HttpContext context) =>
{
var l402 = context.GetL402();
return Results.Ok(new
{
data = "premium content",
paymentHash = l402?.PaymentHash,
caveats = l402?.Caveats,
});
});var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton(new LnBotClient("key_..."));
builder.Services.AddHttpClient("paid-apis")
.AddL402Handler(new L402ClientOptions
{
MaxPrice = 100,
BudgetSats = 50_000,
BudgetPeriod = BudgetPeriod.Day,
});
var app = builder.Build();
app.MapGet("/proxy", async (IHttpClientFactory factory) =>
{
var http = factory.CreateClient("paid-apis");
var data = await http.GetStringAsync("https://api.example.com/premium/data");
return Results.Ok(data);
});using LnBot;
using LnBot.L402;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddSingleton(new LnBotClient("key_..."));
services.AddSingleton<ITokenStore, MemoryTokenStore>();
services.AddHttpClient("paid-apis").AddL402Handler();
var provider = services.BuildServiceProvider();
var http = provider.GetRequiredService<IHttpClientFactory>().CreateClient("paid-apis");
// Auto-pays any 402 responses transparently
var response = await http.GetStringAsync("https://api.example.com/premium/data");
Console.WriteLine(response);using LnBot.L402;
// Parse Authorization: L402 <macaroon>:<preimage>
var auth = L402Headers.ParseAuthorization("L402 mac_base64:preimage_hex");
// → (Macaroon: "mac_base64", Preimage: "preimage_hex")
// Parse WWW-Authenticate: L402 macaroon="...", invoice="..."
var challenge = L402Headers.ParseChallenge("L402 macaroon=\"abc\", invoice=\"lnbc1...\"");
// → (Macaroon: "abc", Invoice: "lnbc1...")
// Format headers
L402Headers.FormatAuthorization("mac", "pre"); // → "L402 mac:pre"
L402Headers.FormatChallenge("mac", "lnbc1..."); // → "L402 macaroon=\"mac\", invoice=\"lnbc1...\""Implement ITokenStore for Redis, file system, or any persistence layer:
public class RedisTokenStore : ITokenStore
{
public Task<L402Token?> GetAsync(string url) { /* ... */ }
public Task SetAsync(string url, L402Token token) { /* ... */ }
public Task DeleteAsync(string url) { /* ... */ }
}
// Register in DI
services.AddSingleton<ITokenStore, RedisTokenStore>();Server middleware makes two SDK calls:
client.L402.CreateChallengeAsync()— creates an invoice + macaroon when a client needs to payclient.L402.VerifyAsync()— verifies an L402 authorization token when a client presents one
Client handler makes one SDK call:
client.L402.PayAsync()— pays a Lightning invoice and returns a ready-to-use Authorization header
- .NET 8+
- An ln.bot API key — create a wallet to get one
LnBot— The .NET SDK this package is built on@lnbot/l402— TypeScript/Express.js equivalent@lnbot/sdk— TypeScript SDK
- ln.bot — website
- Documentation
- L402 specification
- GitHub
MIT