# Lightning Enable Documentation — llms-full.txt > Lightning Enable — infrastructure for agent commerce over Lightning. > A commerce orchestration layer connecting platforms and AI agents to > Bitcoin Lightning payment providers (Strike, OpenNode). Lightning Enable > does not hold funds; the merchant's payment provider facilitates custody > and settlement. This file is the full documentation corpus of https://docs.lightningenable.com, generated from the Markdown sources by docs-site/scripts/generate-llms-full.mjs (do not edit by hand — regenerate with `npm run generate-llms-full` from docs-site/). The legal section (terms of service, privacy policy, data processing agreement) is excluded to keep this file compact; find it at https://docs.lightningenable.com/legal/terms-of-service. For a short curated index instead, see https://docs.lightningenable.com/llms.txt. ============================================================================== # Authentication Source: https://docs.lightningenable.com/api-reference/authentication ============================================================================== # Authentication All Lightning Enable API requests require authentication using an API key. ## API Key Lightning Enable uses a single merchant API key for payment operations and merchant self-service endpoints. Include it in the `X-API-Key` header on every authenticated request. ## Using Your API Key Include your API key in the `X-API-Key` header: ```bash curl -X GET https://api.lightningenable.com/api/payments/inv_123 \ -H "X-API-Key: YOUR_API_KEY" ``` ## API Key Format **Treat your API key as an opaque string.** Do not validate, parse, or pattern-match it in your code — store it exactly as issued and send it back verbatim. For reference, keys issued at signup start with an `lgw_` prefix followed by random characters, while keys issued on regeneration are unprefixed random strings (roughly 44 characters of base64, which may include `+`, `/`, and `=`). Both are equally valid; the server treats every key the same way. The format may change — code that assumes a specific prefix or length will break. ## Obtaining Your API Key When you subscribe to Lightning Enable: 1. Complete the checkout process 2. Your API key is displayed on the success page 3. You can view it (or regenerate it) any time at **Dashboard → Settings** (`/dashboard/settings`) Enterprise customers needing multiple keys or custom onboarding should contact support@lightningenable.com. ## API Key Security ### Best Practices 1. **Never commit API keys to version control** ```bash # .gitignore .env appsettings.Development.json ``` 2. **Use environment variables** ```bash export LIGHTNING_API_KEY="YOUR_API_KEY" ``` ```javascript const apiKey = process.env.LIGHTNING_API_KEY; ``` 3. **Rotate keys periodically** Regenerate your key from the dashboard and update your configuration. 4. **Use separate keys for development and production** Keep your production key secret by using testnet keys during development. ### Storage Recommendations | Environment | Recommendation | |-------------|----------------| | Development | `.env` file (gitignored) | | CI/CD | Secret management (GitHub Secrets, etc.) | | Production | Environment variables or secret manager | ## Key Rotation Regenerate your API key when: - Key may have been compromised - Employee with access leaves - Periodic security rotation Rotate via the Lightning Enable dashboard (**Dashboard → Settings → API Key → Regenerate**, at `/dashboard/settings`) or via `POST /api/merchant/regenerate-key`. The old API key is immediately invalidated on regeneration — update your applications before rotating. ## Error Responses ### Missing API Key ```http HTTP/1.1 401 Unauthorized { "error": "API key required", "message": "Please provide API key in X-API-Key header" } ``` ### Invalid API Key ```http HTTP/1.1 401 Unauthorized { "error": "Invalid API key", "message": "The provided API key is invalid or inactive" } ``` This is also what you receive if your merchant account has been deactivated — an inactive account's key no longer matches any active merchant. Requests that authenticate successfully but hit subscription or feature gates return `403` responses instead; see the [Error Reference](/api-reference/errors#subscription-errors). ## Failed-Authentication Throttling Failed authentication attempts are throttled **per IP address**: more than 20 failures (missing or invalid API key) within a 60-second fixed window blocks further authenticated requests from that IP until the window expires. ```http HTTP/1.1 429 Too Many Requests Retry-After: 42 { "error": "Too many failed authentication attempts", "message": "Slow down and try again in 42 seconds" } ``` If you see this response, your integration is repeatedly sending a wrong or stale key — **fix the key, don't retry**. Retrying with the same bad key records more failures and keeps the block engaged. Verify your key at **Dashboard → Settings**, then wait out the `Retry-After` seconds before the next attempt. This throttle is distinct from the general request rate limiter (whose 429 body says `"Too many requests"` and carries a `retryAfter` field in the JSON body instead of a `Retry-After` header). See [Rate Limiting](/api-reference/rate-limiting#failed-authentication-throttling) for the comparison. ## Testing Authentication First, verify the API is running by calling the public health endpoint (no authentication required): ```bash curl https://api.lightningenable.com/health ``` Expected response: ```json { "status": "Healthy", "totalDuration": 42.15, "checks": [ { "name": "database", "status": "Healthy", "duration": 38.72, "description": null, "exception": null, "tags": ["db", "sql"] } ] } ``` Then verify your API key works by calling an authenticated endpoint: ```bash curl -X GET https://api.lightningenable.com/api/merchant/me \ -H "X-API-Key: YOUR_API_KEY" ``` A `200` response confirms your key is valid. A `401` means the key is invalid or missing. ## Code Examples ### JavaScript ```javascript const API_KEY = process.env.LIGHTNING_API_KEY; async function makeRequest(endpoint) { const response = await fetch(`https://api.lightningenable.com${endpoint}`, { headers: { 'X-API-Key': API_KEY } }); if (response.status === 401) { throw new Error('Invalid API key'); } return response.json(); } ``` ### C# / .NET ```csharp public class LightningEnableClient { private readonly HttpClient _client; public LightningEnableClient(IConfiguration config) { _client = new HttpClient { BaseAddress = new Uri("https://api.lightningenable.com") }; _client.DefaultRequestHeaders.Add("X-API-Key", config["LightningApiKey"]); } public async Task GetAsync(string endpoint) { var response = await _client.GetAsync(endpoint); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); } } ``` ### Python ```python import os import requests API_KEY = os.environ.get('LIGHTNING_API_KEY') BASE_URL = 'https://api.lightningenable.com' def make_request(endpoint): response = requests.get( f'{BASE_URL}{endpoint}', headers={'X-API-Key': API_KEY} ) response.raise_for_status() return response.json() ``` ## Next Steps - [Request Headers](/api-reference/headers) - Idempotency, correlation IDs, and API versioning - [Payments API](/api-reference/payments) - Create payments - [Webhooks](/api-reference/webhooks) - Payment notifications - [Errors](/api-reference/errors) - Error handling ============================================================================== # Error Reference Source: https://docs.lightningenable.com/api-reference/errors ============================================================================== # Error Reference This guide documents the error responses returned by the Lightning Enable API, organized by domain. Use this reference to implement robust error handling in your integration. ## Error Response Format Lightning Enable errors do **not** carry a machine-readable `code` field. Dispatch on the **HTTP status code** first, then on the `error` string if you need finer granularity. Most errors return one of two shapes: **Short form** — a single `error` field: ```json { "error": "Invoice not found" } ``` **Long form** — `error` plus a human-readable `message`: ```json { "error": "Invalid API key", "message": "The provided API key is invalid or inactive" } ``` Some `403 Forbidden` responses from subscription/feature enforcement add snake_case context fields alongside `error` and `message`: | Field | Type | Present on | Description | |-------|------|-----------|-------------| | `error` | string | all errors | Short error identifier (e.g., `"Invalid API key"`, `"Subscription required"`) | | `message` | string | most errors | Human-readable description | | `action_required` | string | 403 subscription/feature errors | What to do next: `contact_support`, `subscribe`, `update_payment_method`, `renew_subscription`, `upgrade_plan` | | `current_plan` | string \| null | some 403 errors | Your current plan tier — one of `free`, `individual`, `l402`. An account carrying a tier value we do not recognize is one exception: that value is echoed back raw so an operator can see what needs fixing. It is `null` when the account has no plan tier on file (a blank tier — for example an admin-created account that has not been assigned one). Handle an unexpected value rather than assuming the enum | | `required_plan` | string \| null | 403 feature errors | Plan tier that would grant the feature, or `null` when no plan grants it | | `subscription_status` | string | some 403 errors | Current Stripe subscription status | | `feature` | string | 403 feature errors | The gated feature identifier | Every error response also includes an `X-Correlation-Id` response header that you can use when contacting support. See [Request Headers](/api-reference/headers) for details. ### Validation Errors (Model Binding) When a request body fails model binding or data-annotation validation (missing required fields, wrong types, out-of-range values), ASP.NET Core returns the standard RFC 9457 validation problem shape — **not** the `{error}` shape above: ```json { "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1", "title": "One or more validation errors occurred.", "status": 400, "errors": { "Amount": [ "The field Amount must be between 0.00000001 and 21000000." ], "OrderId": [ "The OrderId field is required." ] }, "traceId": "00-a1b2c3d4e5f6...-01" } ``` Check the `Content-Type`: validation problems are returned as `application/problem+json`, with per-field messages in the `errors` dictionary. Business-logic failures (e.g., duplicate order ID) use the `{error}` JSON shape instead. ### Unhandled Exceptions Unhandled server errors return a correlation ID for support inquiries: ```json { "error": "An error occurred processing your request.", "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "timestamp": "2026-01-09T12:00:00Z" } ``` The `correlationId` field in the JSON body matches the `X-Correlation-Id` response header. Include this value in any support requests to help us locate your request in our logs. ### Error Sanitization (Production vs. Development) In **production**, error responses for unhandled exceptions are sanitized to prevent leaking internal details. You will only see a generic error message, a correlation ID, and a timestamp. In **development**, additional debugging fields are included: ```json { "error": "An error occurred processing your request.", "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "timestamp": "2026-01-09T12:00:00Z", "detail": "Specific exception message", "exceptionType": "System.InvalidOperationException", "stackTrace": "..." } ``` :::warning The `detail`, `exceptionType`, and `stackTrace` fields are **never** included in production responses. Do not write code that depends on these fields in production. ::: ## HTTP Status Codes ### Success Codes | Code | Meaning | When Used | |------|---------|-----------| | `200 OK` | Request succeeded | GET, PUT, POST (when returning data) | | `201 Created` | Resource created | POST when creating payments, refunds, proxies | | `204 No Content` | Success, no response body | DELETE operations | ### Client Error Codes | Code | Meaning | Common Causes | |------|---------|---------------| | `400 Bad Request` | Invalid request | Missing required fields, invalid format, validation errors, duplicate order ID | | `401 Unauthorized` | Authentication failed | Missing/invalid API key, invalid webhook signature | | `402 Payment Required` | L402 payment needed | Accessing L402-protected endpoints without valid token | | `403 Forbidden` | Access denied | Inactive account, subscription issues, feature not available | | `404 Not Found` | Resource not found | Invalid invoice ID, order ID, proxy ID | | `409 Conflict` | Resource conflict | Rare — only raised by uncaught conflict exceptions. Duplicate order IDs return `400`, not `409` | | `429 Too Many Requests` | Rate limit or auth-failure throttle | Too many requests in the window, or too many failed authentication attempts from your IP | ### Server Error Codes | Code | Meaning | When Used | |------|---------|-----------| | `500 Internal Server Error` | Server error | Unexpected errors, includes correlationId | | `502 Bad Gateway` | Upstream error | Payment provider/Stripe API failures, target API unreachable | | `503 Service Unavailable` | Temporary outage | Maintenance, circuit breaker open | | `504 Gateway Timeout` | Upstream timeout | Payment provider/target API timeout | --- ## Authentication Errors ### Missing API Key **HTTP Status:** `401 Unauthorized` ```json { "error": "API key required", "message": "Please provide API key in X-API-Key header" } ``` **Cause:** Request missing the `X-API-Key` header. **Solution:** Include your API key in every request: ```bash curl -H "X-API-Key: YOUR_API_KEY" https://api.lightningenable.com/api/payments ``` ### Invalid API Key **HTTP Status:** `401 Unauthorized` ```json { "error": "Invalid API key", "message": "The provided API key is invalid or inactive" } ``` **Cause:** API key doesn't match any active merchant account. **Solution:** - Verify you're using the correct API key (view it at **Dashboard → Settings**) - Check if your account is active - Contact support if you've lost your API key ### Too Many Failed Authentication Attempts **HTTP Status:** `429 Too Many Requests` **Header:** `Retry-After: ` ```json { "error": "Too many failed authentication attempts", "message": "Slow down and try again in 42 seconds" } ``` **Cause:** More than 20 failed authentication attempts from your IP within a 60-second window. This is a per-IP throttle, separate from the general rate limiter. **Solution:** Fix your API key — do **not** retry with the same key. Retrying failed authentications only keeps the throttle engaged. See [Failed-Authentication Throttling](/api-reference/rate-limiting#failed-authentication-throttling). ### Server Configuration Error **HTTP Status:** `500 Internal Server Error` ```json { "error": "Server configuration error" } ``` **Cause:** Server configuration issue on the Lightning Enable side. **Solution:** Contact support@lightningenable.com with your correlation ID. --- ## Subscription Errors ### Account Inactive **HTTP Status:** `403 Forbidden` ```json { "error": "Account inactive", "message": "Your account is inactive. Please contact support.", "action_required": "contact_support" } ``` **Cause:** Merchant account has been deactivated. **Solution:** Contact support@lightningenable.com to reactivate. ### Subscription Required **HTTP Status:** `403 Forbidden` ```json { "error": "Subscription required", "message": "Your plan tier requires an active subscription. Please subscribe to continue using the service.", "current_plan": "individual", "action_required": "subscribe" } ``` **Cause:** Paid-tier account without a valid Stripe subscription. **Solution:** Subscribe at [lightningenable.com](https://lightningenable.com). ### Subscription Not Active **HTTP Status:** `403 Forbidden` ```json { "error": "Subscription not active", "message": "Your subscription payment is past due. Please update your payment method to continue using the service.", "subscription_status": "past_due", "action_required": "update_payment_method" } ``` **Possible Status Values:** | Status | Message summary | `action_required` | |--------|-----------------|-------------------| | `past_due` | Payment is past due | `update_payment_method` | | `canceled` | Subscription canceled | `renew_subscription` | | `unpaid` | Subscription unpaid | `renew_subscription` | | `incomplete` | Setup incomplete | `renew_subscription` | | `incomplete_expired` | Setup expired | `renew_subscription` | :::note `action_required` is `update_payment_method` only for `past_due`; every other inactive status returns `renew_subscription`. The `message` text varies per status. ::: ### Subscription Period Expired **HTTP Status:** `403 Forbidden` ```json { "error": "Subscription period expired", "message": "Your subscription billing period has expired. Please renew your subscription to continue using the service.", "subscription_status": "active", "current_period_end": "2026-01-15T00:00:00.0000000Z", "action_required": "renew_subscription" } ``` **Cause:** The subscription's `CurrentPeriodEnd` has passed even though the status may still show `active`. This typically occurs when a Stripe webhook is delayed. The middleware catches expired billing periods as a safety net. **Solution:** Renew your subscription or wait for Stripe to process the renewal. If your payment method is valid, this should resolve automatically once the Stripe webhook updates the billing period. ### Feature Not Available **HTTP Status:** `403 Forbidden` ```json { "error": "Feature not available", "message": "L402 Agentic Commerce is not enabled for your plan. Upgrade to an Agentic Commerce plan to use the L402 producer API.", "feature": "l402", "current_plan": "individual", "required_plan": "individual", "action_required": "upgrade_plan" } ``` **Gated features:** | `feature` | Endpoints | `required_plan` | `action_required` | |-----------|-----------|-----------------|-------------------| | `refunds` | `/api/refunds` | `null` | `contact_support` | | `multi_currency` | `/api/payments/*convert*` | `individual` | `upgrade_plan` | | `l402` | `/api/l402/challenges` | `individual` | `upgrade_plan` | Refunds send `required_plan: null` because no plan grants them. Refunds are an operator-granted per-account flag, so upgrading would not turn them on — contact support instead. :::note `current_plan` can equal `required_plan` Each gate reads a per-account flag, not the plan table. All three live tiers include L402, so an account whose `l402Enabled` flag was never applied — for example a row still stored under a retired tier id, which now reports as `individual` — sees `current_plan` and `required_plan` name the same tier. Contact support to have the flag applied rather than buying the plan again. ::: See [Subscription & Plan Enforcement](/products/subscription-management) for full details on plan tiers and feature gating. --- ## Payment Errors ### Payment Not Found **HTTP Status:** `404 Not Found` ```json { "error": "Invoice 12345 not found for merchant 42" } ``` **Cause:** Invoice ID doesn't exist or doesn't belong to your merchant account. Looking up a payment by order ID returns the same shape with an order-specific message (`"Invoice not found for OrderId ORDER-123"`). **Solution:** Verify the invoice ID is correct and belongs to your account. ### Invalid Payment Request **HTTP Status:** `400 Bad Request` Business-rule failures return the `{error}` shape with the specific reason: ```json { "error": "Merchant 42 not found or inactive" } ``` Field-level validation failures (amount out of range, missing order ID, malformed URL) return the [validation problem shape](#validation-errors-model-binding) instead. ### Duplicate Order **HTTP Status:** `400 Bad Request` ```json { "error": "Invoice already exists for OrderId ORDER-123" } ``` **Cause:** A payment with this order ID already exists for your merchant account. **Solution:** Use unique order IDs for each payment request. To safely retry a payment creation without risking duplicates, send an `X-Idempotency-Key` header — see [Request Headers](/api-reference/headers). :::note Duplicate orders return `400 Bad Request`, not `409 Conflict`. Match on the HTTP status plus the `"Invoice already exists"` prefix of the `error` string if you need to detect this case programmatically. ::: --- ## Refund Errors ### Invalid Refund Request **HTTP Status:** `400 Bad Request` ```json { "error": "Cannot refund invoice in status 'pending'. Only paid, underpaid, processing, or partially-refunded payments can be refunded." } ``` **Refundable Statuses:** Only invoices with status `paid`, `underpaid`, `processing`, or `refunded` (for partial refunds) can be refunded. ### Refund Not Found **HTTP Status:** `404 Not Found` ```json { "error": "Refund 67890 not found for merchant 42" } ``` ### Invoice Not Found (for refund) **HTTP Status:** `400 Bad Request` (on refund creation) or `404 Not Found` (when listing refunds by invoice) ```json { "error": "Invoice 12345 not found for merchant 42" } ``` --- ## L402 Protocol Errors ### Payment Required (402) **HTTP Status:** `402 Payment Required` **Headers:** ```http WWW-Authenticate: L402 macaroon="AgEB...", invoice="lnbc..." X-L402-Error: No Authorization header provided ``` **Body:** ```json { "error": "Payment Required", "message": "Pay the Lightning invoice to access this API", "proxy": { "id": "my-api-1234", "name": "My API", "description": "AI services monetized with Lightning" }, "l402": { "macaroon": "AgEBYXBpLmxpZ2h0bmluZ2VuYWJsZS5jb20...", "invoice": "lnbc100n1pj...", "amount_sats": 100, "payment_hash": "abc123...", "expires_at": "2026-01-09T13:00:00Z" }, "instructions": { "step1": "Pay the Lightning invoice using any Lightning wallet", "step2": "Copy the preimage (proof of payment) from your wallet", "step3": "Include in request: Authorization: L402 : (or Authorization: Payment method=\"lightning\", preimage=\"\")" } } ``` ### Invalid L402 Credential **HTTP Status:** `402 Payment Required` **Header:** `X-L402-Error: Invalid L402 format. Expected: L402 :` **Common L402 Errors:** | Error Message | Cause | |---------------|-------| | No Authorization header provided | Missing Authorization header | | Invalid authorization scheme | Using Basic/Bearer instead of L402 | | Invalid L402 format | Malformed macaroon:preimage format | | Preimage does not match payment hash | Incorrect preimage | | L402 verification failed | Invalid or expired macaroon | ### Proxy Not Found **HTTP Status:** `404 Not Found` ```json { "error": "Proxy not found", "message": "No active proxy configuration found for ID: invalid-proxy" } ``` ### Proxy Unavailable **HTTP Status:** `404 Not Found` ```json { "error": "Proxy unavailable", "message": "This API proxy is currently unavailable" } ``` **Cause:** The merchant account owning the proxy is inactive. ### L402 Proxy Gateway Errors **Bad Gateway (502):** ```json { "error": "Bad Gateway", "message": "Unable to connect to the target API", "proxy_id": "my-api-1234", "details": "Connection refused" } ``` **Gateway Timeout (504):** ```json { "error": "Gateway Timeout", "message": "The target API did not respond in time", "proxy_id": "my-api-1234" } ``` --- ## Webhook Errors ### Invalid Webhook Payload **HTTP Status:** `400 Bad Request` ```json { "error": "Invalid JSON", "details": "Invalid webhook payload format" } ``` ### Invalid Payload Structure **HTTP Status:** `400 Bad Request` ```json { "error": "Invalid payload" } ``` ### Invoice Not Found (Webhook) **HTTP Status:** `404 Not Found` ```json { "error": "Invoice not found" } ``` **Cause:** Webhook received for unknown charge ID (provider charge ID not matched). ### Invalid Webhook Signature **HTTP Status:** `401 Unauthorized` ```json { "error": "Invalid signature" } ``` **Cause:** Payment provider webhook signature verification failed. **Solution:** Ensure your payment provider API key and webhook secret are correctly configured. ### Webhook Signature Required (Production) **HTTP Status:** `401 Unauthorized` ```json { "error": "Webhook signature verification required in production" } ``` **Cause:** In production, all webhooks must include verifiable signatures. ### Invalid Stripe Signature **HTTP Status:** `400 Bad Request` ```json { "error": "Invalid signature" } ``` **Cause:** Stripe webhook signature verification failed. --- ## Rate Limiting Errors ### Rate Limit Exceeded **HTTP Status:** `429 Too Many Requests` ```json { "error": "Too many requests", "message": "Rate limit exceeded. Please try again later.", "retryAfter": 60 } ``` :::info The general rate limiter does **not** emit `X-RateLimit-*` or `Retry-After` headers. The wait time is in the JSON body's `retryAfter` field (seconds). Only the [auth-failure throttle](#too-many-failed-authentication-attempts) sets a `Retry-After` header. ::: **Rate Limits by Policy:** | Policy | Limit | Window | Endpoints | |--------|-------|--------|-----------| | Global | 100 | 1 min | All requests (per API key, or per IP when anonymous) | | Read | 200 | 1 min | GET operations | | Payment Create | 10 | 1 min | POST /api/payments, POST /api/refunds, /api/checkout/* | | Write | 20 | 1 min | Merchant self-service writes (e.g., key regeneration) | | Checkout Create | 5 | 1 min | Stripe checkout session creation | | Admin | 30 | 1 min | Internal admin endpoints | **Solution:** Wait for the number of seconds in the body's `retryAfter` field, then retry. See [Rate Limiting](/api-reference/rate-limiting) for backoff strategies. ### Auth-Failure Throttle (Distinct 429) A separate 429 — see [Too Many Failed Authentication Attempts](#too-many-failed-authentication-attempts) under Authentication Errors above. Distinguish the two by the `error` string: `"Too many requests"` (rate limiter, wait and retry) vs. `"Too many failed authentication attempts"` (auth throttle, fix your key). --- ## Payment Provider Integration Errors These errors arise when Lightning Enable cannot communicate with the configured payment provider (Strike or OpenNode). ### Payment Provider API Error **HTTP Status:** `502 Bad Gateway` ```json { "error": "Payment provider API error", "details": "Invalid API key" } ``` **Common Errors:** - Invalid API key - Insufficient balance - Invalid charge request - Rate limited by provider ### Payment Provider Timeout **HTTP Status:** `504 Gateway Timeout` ```json { "error": "Payment provider request timed out" } ``` **Solution:** Retry the request. Provider requests have a 30-second timeout. ### Payment Provider Circuit Breaker Open When the payment provider experiences multiple consecutive failures, the circuit breaker opens: **HTTP Status:** `503 Service Unavailable` The circuit breaker: - Opens after 5 consecutive failures - Stays open for 30 seconds - Automatically tests recovery in half-open state --- ## Strike API Errors Errors specific to merchants using Strike as their payment provider. ### Strike Authentication Failure **HTTP Status:** `502 Bad Gateway` ```json { "error": "Payment provider API error", "details": "Strike authentication failed: 401" } ``` **Cause:** Invalid or expired Strike API key. **Solution:** Verify your Strike API key via the admin API or contact support. Strike API keys are configured per-merchant via `strikeApiKey`. ### Strike Rate Limited **HTTP Status:** `429 Too Many Requests` (from Strike, surfaced as `502`) ```json { "error": "Payment provider API error", "details": "Strike rate limit exceeded" } ``` **Solution:** Lightning Enable automatically retries with exponential backoff (3 retries). If this error persists, reduce request frequency. ### Strike Webhook Entity Not Found **HTTP Status:** `404 Not Found` ```json { "error": "Invoice not found" } ``` **Cause:** Strike webhooks carry only an `entityId`. Lightning Enable fetches the full payment details from Strike after receipt. This error means the entity could not be found on follow-up fetch. **Note:** Strike webhooks are "thin" — the controller must fetch full payment details from the Strike API after receiving a webhook event. --- ## OpenNode Integration Errors Errors specific to merchants using OpenNode as their payment provider. ### OpenNode API Error **HTTP Status:** `502 Bad Gateway` ```json { "error": "Payment provider API error", "details": "Invalid API key" } ``` **Common OpenNode Errors:** - Invalid API key - Insufficient balance - Invalid charge request - Rate limited by OpenNode ### OpenNode Timeout **HTTP Status:** `504 Gateway Timeout` ```json { "error": "Payment provider request timed out" } ``` **Solution:** Retry the request. OpenNode has a 30-second timeout. ### OpenNode Circuit Breaker Open When OpenNode experiences multiple consecutive failures, the circuit breaker opens: **HTTP Status:** `503 Service Unavailable` The circuit breaker: - Opens after 5 consecutive failures - Stays open for 30 seconds - Automatically tests recovery in half-open state --- ## Stripe Integration Errors ### Checkout Session Errors **HTTP Status:** `400 Bad Request` ```json { "error": "Failed to create checkout session. Please try again." } ``` **Common Causes:** - Invalid email format - Missing required fields - Stripe API error ### Subscription Result Errors **Missing Session ID:** ```json { "error": "session_id is required" } ``` **Session Not Found:** ```json { "error": "Checkout session not found" } ``` **Payment Not Completed:** ```json { "error": "Payment not completed", "paymentStatus": "unpaid" } ``` **Customer Not Found:** ```json { "error": "Customer not found for this session" } ``` **Merchant Not Found:** ```json { "error": "Merchant account not found. Please wait a moment and try again." } ``` ### Customer Portal Errors **HTTP Status:** `401 Unauthorized` ```json { "error": "API key authentication required" } ``` **HTTP Status:** `400 Bad Request` ```json { "error": "No Stripe customer ID associated with this account" } ``` --- ## Proxy Management Errors ### Invalid Target URL **HTTP Status:** `400 Bad Request` ```json { "error": "Invalid target URL. Must be a valid HTTP or HTTPS URL." } ``` ### Invalid Path Pattern **HTTP Status:** `400 Bad Request` ```json { "error": "Invalid path pattern. Must start with '/' and be a valid glob pattern." } ``` ### Proxy Not Found **HTTP Status:** `404 Not Found` ```json { "error": "Proxy not found" } ``` ### Endpoint Pricing Not Found **HTTP Status:** `404 Not Found` ```json { "error": "Endpoint pricing not found" } ``` --- ## Merchant Settings Errors ### Authentication Required **HTTP Status:** `401 Unauthorized` ```json { "error": "Authentication required" } ``` ### Merchant Not Found **HTTP Status:** `404 Not Found` ```json { "error": "Merchant not found" } ``` ### Invalid Payment Provider Key **HTTP Status:** `400 Bad Request` ```json { "error": "Payment provider API key is required" } ``` ### Invalid Webhook URL **HTTP Status:** `400 Bad Request` ```json { "error": "Invalid webhook URL format" } ``` --- ## Error Handling Best Practices ### 1. Always Check HTTP Status First ```javascript const response = await fetch(url, options); if (!response.ok) { const error = await response.json(); throw new ApiError(response.status, error); } return response.json(); ``` ### 2. Dispatch on Status, Then the Error String There is no machine-readable `code` field. Branch on the HTTP status first; use the `error` string (and context fields like `action_required`) when you need to distinguish causes within a status: ```javascript class ApiError extends Error { constructor(status, data) { super(data.message || data.error); this.status = status; this.error = data.error; this.retryAfter = data.retryAfter; this.actionRequired = data.action_required; } } function handleError(err) { switch (err.status) { case 401: return showConfigurationError('Check your API key'); case 403: // action_required tells you what to do: subscribe, // update_payment_method, renew_subscription, upgrade_plan... return handleSubscriptionIssue(err.actionRequired); case 429: if (err.error === 'Too many failed authentication attempts') { // Auth throttle — fix the key, don't retry return showConfigurationError('Check your API key'); } // General rate limit — wait retryAfter seconds from the body return scheduleRetry(err.retryAfter || 60); case 400: if (err.error?.startsWith('Invoice already exists')) { return handleDuplicateOrder(err); } return logAndAlert(err); default: return logAndAlert(err); } } ``` ### 3. Implement Retry Logic ```javascript async function withRetry(fn, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (error) { if (!isRetryable(error) || attempt === maxRetries) { throw error; } const delay = Math.min(1000 * Math.pow(2, attempt), 30000); await new Promise(r => setTimeout(r, delay)); } } } function isRetryable(error) { // Retry server errors and general rate limits if (error.status >= 500) return true; if (error.status === 429 && error.error !== 'Too many failed authentication attempts') { return true; } return false; } ``` ### 4. Log Errors with Context ```javascript function logError(error, context) { console.error({ timestamp: new Date().toISOString(), status: error.status, error: error.error, message: error.message, correlationId: error.correlationId, context: { endpoint: context.endpoint, orderId: context.orderId, merchantId: context.merchantId } }); } ``` ### 5. User-Friendly Messages Map the HTTP status (plus the `error` string where needed) to your own user-facing copy: ```javascript function getUserMessage(err) { switch (err.status) { case 401: return 'Authentication failed. Please check your settings.'; case 402: return 'Payment required to access this resource.'; case 403: return 'Please check your subscription status.'; case 404: return 'Not found. It may have expired.'; case 429: return 'Too many requests. Please wait a moment.'; case 502: case 503: case 504: return 'Payment service temporarily unavailable.'; default: return 'An error occurred. Please try again.'; } } ``` --- ## Retry Strategy Summary | Error Type | Retry? | Strategy | |------------|--------|----------| | 400 Bad Request | No | Fix request data | | 401 Unauthorized | No | Fix credentials | | 402 Payment Required | No | Complete payment | | 403 Forbidden | No | Check subscription/features | | 404 Not Found | No | Check resource ID | | 429 Rate Limited | Yes | Wait `retryAfter` seconds (from the JSON body) | | 429 Auth Throttle | No | Fix your API key first — retrying keeps the throttle engaged | | 500 Server Error | Yes | Exponential backoff | | 502 Bad Gateway | Yes | Exponential backoff | | 503 Service Unavailable | Yes | Wait, then retry | | 504 Gateway Timeout | Yes | Retry immediately | --- ## Next Steps - [Rate Limiting](/api-reference/rate-limiting) - Detailed rate limit information - [Authentication](/api-reference/authentication) - API key setup and security - [Webhooks](/api-reference/webhooks) - Webhook configuration and signatures - [Payments API](/api-reference/payments) - Payment creation and status ============================================================================== # Request Headers Source: https://docs.lightningenable.com/api-reference/headers ============================================================================== # Request Headers Lightning Enable uses several custom HTTP headers for authentication, idempotency, request tracing, and versioning. ## Header Summary | Header | Direction | Required | Description | |--------|-----------|----------|-------------| | `X-API-Key` | Request | Yes | Merchant API key | | `X-Idempotency-Key` | Request | No | Prevents duplicate operations on payment endpoints | | `Idempotency-Key` | Request | No | Standard spelling, accepted on `POST /api/l402/challenges` — see [L402 challenge idempotency](#l402-challenge-idempotency) | | `X-Correlation-Id` | Request & Response | No | Distributed tracing identifier | | `X-API-Version` | Response | -- | API version running on the server | | `X-API-Deprecation` | Response | -- | Deprecation notice (reserved for future use) | | `X-Idempotency-Replayed` | Response | -- | Indicates a replayed idempotent response was returned | | `X-Total-Count` | Response | -- | Total rows matching a paged listing, ignoring paging | --- ## X-Idempotency-Key Use the `X-Idempotency-Key` header to safely retry payment and refund requests without creating duplicates. If you send the same idempotency key twice, the API returns the original cached response instead of processing the request again. ### How It Works 1. Generate a unique key (UUID recommended) on the client side. 2. Include it as `X-Idempotency-Key` in your request. 3. If the request succeeds (2xx), the response is cached for **24 hours** keyed by your merchant ID + idempotency key. 4. If you retry with the same key within 24 hours, the cached response is returned immediately with the header `X-Idempotency-Replayed: true`. ### Supported Endpoints | Endpoint | Method | Description | |----------|--------|-------------| | `/api/payments` | POST | Create payment invoice | | `/api/refunds` | POST | Create refund | | `/api/checkout/sessions` | POST | Create checkout session | ### Constraints - Maximum key length: **256 characters** - Keys are scoped per merchant (two different merchants can use the same key without conflict) - Only **successful responses** (2xx) are cached. If the request fails, you can safely retry with the same key. - Cached responses expire after **24 hours** ### Example ```bash # First request - creates the payment curl -X POST https://api.lightningenable.com/api/payments \ -H "X-API-Key: le_merchant_abc123" \ -H "X-Idempotency-Key: order-12345-attempt-1" \ -H "Content-Type: application/json" \ -d '{ "orderId": "ORDER-12345", "amount": 49.99, "currency": "USD" }' # Response: 201 Created { "invoiceId": "inv_abc123", ... } # Retry with same key - returns cached response curl -X POST https://api.lightningenable.com/api/payments \ -H "X-API-Key: le_merchant_abc123" \ -H "X-Idempotency-Key: order-12345-attempt-1" \ -H "Content-Type: application/json" \ -d '{ "orderId": "ORDER-12345", "amount": 49.99, "currency": "USD" }' # Response: 201 Created { "invoiceId": "inv_abc123", ... } # Response header: X-Idempotency-Replayed: true ``` ### Error Responses | Scenario | Status | Response | |----------|--------|----------| | Key exceeds 256 characters | `400 Bad Request` | `{ "error": "Idempotency key must not exceed 256 characters." }` | ### Best Practices 1. **Use UUIDs or deterministic keys.** A UUID per request attempt works well. Alternatively, use a deterministic key like `{orderId}-{action}` so retries naturally reuse the same key. 2. **Do not reuse keys across different operations.** Each logically distinct operation should have its own key. 3. **Always retry on network errors.** If you never received a response, it is safe to retry with the same idempotency key -- you will either get the cached result or the request will process for the first time. --- ## L402 challenge idempotency `POST /api/l402/challenges` has its own idempotency, and it works differently from the response cache above — it is anchored on the minted challenge rather than on a cached HTTP response, so a retry gets back the same **invoice**, not a replayed response body that may no longer be payable. | | Payment endpoints | `POST /api/l402/challenges` | |---|---|---| | Header | `X-Idempotency-Key` | `Idempotency-Key` (or `X-Idempotency-Key`) | | Max key length | 256 characters | 200 characters | | Over-long key | `400` | `400` | | What is replayed | The cached 2xx response | The minted challenge itself — same invoice, macaroon, and payment hash | | Window | 24 hours | The life of the invoice (10 minutes by default) | | Survives a restart | No (process cache) | Yes (stored on the challenge) | | Same key, different body | Cached response returned | `409` if the resource or price differs and the invoice is still live; a fresh mint once it has expired | The key can also be sent as an `idempotencyKey` body field. Replays carry `X-Idempotency-Replayed: true`. Full rules: [Producer API Reference → Idempotency](/products/agentic-commerce/producer-api-reference#idempotency). --- ## X-Correlation-Id The `X-Correlation-Id` header enables distributed request tracing across your systems and the Lightning Enable API. ### How It Works - **If you send** an `X-Correlation-Id` header with your request, the API uses your value throughout the request lifecycle. - **If you do not send one**, the API generates a new UUID automatically. - The correlation ID is **always returned** in the response `X-Correlation-Id` header. - The same ID appears in the `correlationId` field of error responses for unhandled exceptions. - All server-side log entries for the request include this ID. ### Example ```bash # Send your own correlation ID curl -X GET https://api.lightningenable.com/api/payments/inv_abc123 \ -H "X-API-Key: le_merchant_abc123" \ -H "X-Correlation-Id: my-trace-id-12345" # Response headers include: # X-Correlation-Id: my-trace-id-12345 ``` ```bash # Let the API generate one curl -X GET https://api.lightningenable.com/api/payments/inv_abc123 \ -H "X-API-Key: le_merchant_abc123" # Response headers include: # X-Correlation-Id: a1b2c3d4-e5f6-7890-abcd-ef1234567890 ``` ### Best Practices 1. **Generate a unique correlation ID per request** in your client and include it in every API call. 2. **Log the correlation ID** on your side so you can cross-reference with Lightning Enable logs if you contact support. 3. **Pass correlation IDs through your own microservices** to enable end-to-end tracing. --- ## X-API-Version Every API response includes an `X-API-Version` header indicating the version of the Lightning Enable API that served the request. ```http HTTP/1.1 200 OK X-API-Version: 1.0.0 ``` ### Usage - Use this header to verify which API version you are communicating with. - The version follows [semantic versioning](https://semver.org/) (major.minor.patch). - Breaking changes will increment the major version. ### X-API-Deprecation (Reserved) The `X-API-Deprecation` response header is reserved for future use. When an endpoint or API version is deprecated, this header will contain a human-readable deprecation notice with a migration deadline. Currently, no endpoints are deprecated. --- ## Next Steps - [Authentication](/api-reference/authentication) - API key setup - [Errors](/api-reference/errors) - Error handling and correlation IDs - [Rate Limiting](/api-reference/rate-limiting) - Rate limit headers and policies ============================================================================== # L402 API Source: https://docs.lightningenable.com/api-reference/l402 ============================================================================== # L402 API L402 (formerly LSAT) enables pay-per-request API access using Lightning Network payments. ## Overview L402 is a protocol for HTTP 402 Payment Required responses that enables: - **Pay-per-request** API access without subscriptions - **Anonymous access** - no accounts or credit cards needed - **Instant micropayments** via Lightning Network - **Cryptographic verification** using macaroons and preimages Lightning Enable speaks two schemes on the same 402: classic **L402** (macaroon + preimage) and the IETF **`Payment`** scheme (MPP, Machine Payments Protocol). One invoice backs both, so any client can pick the one it implements. See [Payment (MPP) credentials](#payment-mpp-credentials). ## Endpoints ### Get L402 Pricing Get pricing information for L402-protected endpoints. ```http GET /api/l402/pricing ``` #### Response ```json { "defaultPriceSats": 10, "serviceName": "lightning-enable", "endpoints": [ { "pathPattern": "/api/premium/*", "priceSats": 100, "description": "Premium API access" }, { "pathPattern": "/l402/proxy/*", "priceSats": "varies", "description": "Proxy pricing set per-proxy" } ], "tokenValiditySeconds": 3600 } ``` ### Check L402 Status Check if a request has valid L402 authentication. ```http GET /api/l402/status ``` #### Headers | Header | Required | Description | |--------|----------|-------------| | `Authorization` | No | `L402 :` | #### Response (Authenticated) ```json { "authenticated": true, "paymentHash": "abc123...", "expiresAt": "2024-12-29T13:00:00Z", "remainingRequests": null } ``` #### Response (Not Authenticated) ```json { "authenticated": false, "message": "L402 credential required" } ``` ### L402 Protected Proxy Access proxied APIs with L402 payment. ```http * /l402/proxy/{proxyId}/{path} ``` #### Without L402 Credential Returns 402 Payment Required: ```json { "error": "Payment Required", "message": "Pay the Lightning invoice to access this resource", "l402": { "macaroon": "AgELbGlnaHRuaW5nLWVuYWJsZQJCMDAwMDAwMD...", "invoice": "lnbc100n1pnxyz...", "amount_sats": 10, "payment_hash": "abc123def456...", "expires_at": "2024-12-29T13:00:00Z" } } ``` #### With Valid L402 Credential ```bash curl https://api.lightningenable.com/l402/proxy/{proxyId}/endpoint \ -H "Authorization: L402 AgEL...:abc123..." ``` Returns the proxied API response. ## L402 Authentication Flow ### Step 1: Request Protected Resource ```bash curl https://api.lightningenable.com/l402/proxy/my-api/data ``` ### Step 2: Receive 402 Challenge ```http HTTP/1.1 402 Payment Required WWW-Authenticate: L402 macaroon="AgEL...", invoice="lnbc..." { "error": "Payment Required", "l402": { "macaroon": "AgEL...", "invoice": "lnbc100n1p...", "amount_sats": 10, "payment_hash": "abc123..." } } ``` ### Step 3: Pay Lightning Invoice Pay the invoice using any Lightning wallet. You'll receive a **preimage** (proof of payment). ### Step 4: Access with Credential Combine macaroon and preimage: ```bash curl https://api.lightningenable.com/l402/proxy/my-api/data \ -H "Authorization: L402 AgEL...:abc123def456789..." ``` ### Step 5: Receive Response ```http HTTP/1.1 200 OK { "data": "Your requested content" } ``` ## Credential Format The L402 credential consists of two parts: ``` Authorization: L402 : ``` | Component | Format | Description | |-----------|--------|-------------| | `macaroon` | Base64 | Bearer token with caveats | | `preimage` | Hex (64 chars) | 32-byte proof of payment | ### Verification The server verifies the credential in the following order: 1. **Preimage matches hash**: `SHA256(preimage) == payment_hash` 2. **Macaroon signature**: HMAC-SHA256 verification ensures the token was not tampered with 3. **All caveats satisfied**: `expires` (not expired), `path` (matches request path), `merchant_id` (matches request merchant), `amount_sats` (matches endpoint price) ## Token Reuse L402 tokens can be reused until they expire, but only for the same endpoint, merchant, and price tier they were issued for. The `path`, `merchant_id`, and `amount_sats` caveats are checked on every request, so a token cannot be reused across different contexts. ```javascript // Save credential after first payment const credential = `${macaroon}:${preimage}`; localStorage.setItem('l402_credential', credential); // Reuse for subsequent requests to the SAME endpoint const savedCredential = localStorage.getItem('l402_credential'); fetch(url, { headers: { 'Authorization': `L402 ${savedCredential}` } }); ``` **Default token validity:** 1 hour (configurable per endpoint) :::tip When caching credentials, key them by the full endpoint path (not just the host) since tokens are path-bound. A token issued for `/l402/proxy/api-a/data` will be rejected if used against `/l402/proxy/api-b/data`. ::: ## Macaroon Structure Macaroons are cryptographic bearer tokens signed with HMAC-SHA256. Each macaroon contains an identifier, a set of caveats, and a signature. Lightning Enable embeds security caveats at issuance time that bind the token to a specific context, preventing reuse across endpoints, merchants, or price tiers. ```json { "identifier": "lightning-enable:payment_hash:timestamp", "caveats": [ "services = lightning-enable:0", "path = /l402/proxy/my-api/data", "merchant_id = 42", "charge_id = abc123-def456", "amount_sats = 100", "expires = 1704067200" ], "signature": "hmac-sha256" } ``` ### Caveat Types Every macaroon issued by Lightning Enable includes the following caveats. During verification, **all** caveats must be satisfied for the token to be accepted. An unknown or unsatisfied caveat causes verification to fail. | Caveat | Example | Description | |--------|---------|-------------| | `services` | `services = lightning-enable:0` | Service identifier and tier. | | `path` | `path = /l402/proxy/my-api/data` | Binds the token to the API path it was issued for. A token issued for `/api/premium/v1` cannot be used against `/api/premium/v2`. Wildcard paths (e.g., `/l402/proxy/my-api/*`) allow access to any sub-path under the prefix. | | `merchant_id` | `merchant_id = 42` | Binds the token to the issuing merchant. Prevents cross-tenant token reuse -- a token issued by Merchant A cannot be replayed against Merchant B's endpoints. Both directions are enforced: if the request has a merchant context, the token must contain a matching `merchant_id` caveat, and vice versa. | | `charge_id` | `charge_id = abc123-def456` | The OpenNode charge ID associated with the payment. | | `amount_sats` | `amount_sats = 100` | Binds the token to the price at issuance. Prevents a token purchased at a lower price (e.g., 10 sats for a demo endpoint) from being reused against a higher-priced endpoint (e.g., 100 sats for premium data) that happens to share a wildcard path pattern. | | `expires` | `expires = 1704067200` | Unix timestamp after which the token is no longer valid. Default validity is 1 hour (configurable per endpoint). | ### Caveat Verification When a client presents an L402 credential, Lightning Enable performs the following checks in order: 1. **Preimage verification** -- `SHA256(preimage) == payment_hash` (proves payment was made) 2. **Macaroon signature** -- HMAC-SHA256 verification (proves the token was not tampered with) 3. **Caveat satisfaction** -- each caveat is evaluated against the current request context: - `expires`: the current time must be before the expiration timestamp - `path`: the request path must match the bound path (exact or wildcard prefix) - `merchant_id`: the request's merchant context must match the bound merchant ID - `amount_sats`: the endpoint's current price must match the bound amount - Any unrecognized caveat causes the verification to **fail** (closed-world assumption) If any check fails, the server returns an appropriate error (401 or 403) with a description of the failure. ## Error Responses ### 402 Payment Required ```json { "error": "Payment Required", "message": "Pay the Lightning invoice to access this resource", "l402": { "macaroon": "...", "invoice": "...", "amount_sats": 10 } } ``` ### 401 Invalid Credential ```json { "error": "Unauthorized", "message": "Invalid L402 credential", "details": "Preimage does not match payment hash" } ``` ### 403 Token Expired ```json { "error": "Forbidden", "message": "L402 token has expired", "details": "Token expired at 2024-12-29T12:00:00Z" } ``` ### 403 Path Not Allowed ```json { "error": "Forbidden", "message": "Token not valid for this path", "allowed": "/l402/proxy/api-a/*", "requested": "/l402/proxy/api-b/data" } ``` ## Payment (MPP) credentials Alongside L402, every 402 also advertises the HTTP **`Payment`** authentication scheme, the format shared by [`draft-httpauth-payment-00`](https://datatracker.ietf.org/doc/draft-httpauth-payment/) (the core scheme) and [`draft-lightning-charge-00`](https://datatracker.ietf.org/doc/draft-lightning-charge/) (the Lightning `charge` method). This is usually called **MPP** (Machine Payments Protocol). Three credential profiles are accepted, and all three are proofs of the same invoice: | Profile | `Authorization` header | Reusable? | Best for | |---|---|---|---| | Classic L402 | `L402 :` | Yes, until the macaroon expires | Existing L402 clients, LND `lnget`, Aperture-style tooling | | Legacy `Payment` (auth-params) | `Payment method="lightning", preimage=""` | Yes, until the challenge expires | Clients that only want to send a preimage | | Modern `Payment` (bearer token) | `Payment ` | **No. Single use** | Clients built on the current drafts (mppx, `l402-requests`, the Lightning Enable MCP server v1.24+) | You never opt in per merchant. The server emits both schemes for every L402-gated resource, and a client uses whichever one it implements. ### The 402 challenge A single 402 carries one `WWW-Authenticate` header per scheme. The `Payment` header is a superset: it holds the modern parameters and the legacy `invoice` / `amount` / `currency` parameters in the same value, and both drafts require clients to ignore parameters they don't know. ```http HTTP/1.1 402 Payment Required WWW-Authenticate: L402 macaroon="AgEL...", invoice="lnbc1u1p..." WWW-Authenticate: Payment id="k9Q3...", realm="lightning-enable", method="lightning", intent="charge", request="eyJhbW91bnQiOiIxMDAiLC...", expires="2026-09-11T18:30:00Z", invoice="lnbc1u1p...", amount="100", currency="sat" Cache-Control: no-store ``` | Parameter | Meaning | |---|---| | `id` | Server-computed binding over every other parameter. Verification recomputes it from the fields you echo back, so any edit to the challenge invalidates the credential. | | `realm` | Always `lightning-enable` on the hosted API. | | `method` / `intent` | Always `lightning` / `charge`. | | `request` | base64url of a JCS-canonical JSON object: `{"amount":"100","currency":"sat","methodDetails":{"invoice":"lnbc...","network":"mainnet","paymentHash":""}}`. Echo it byte for byte. | | `expires` | RFC 3339 UTC. Never later than the BOLT11 invoice expiry. A modern credential must be redeemed before this instant. | | `digest` | Present only on proxied `POST` / `PUT` / `PATCH` requests with a body: the RFC 9530 `Content-Digest` of that body, bound into `id`. Resend the identical body when you redeem. | | `invoice`, `amount`, `currency` | Legacy parameters for clients that predate the drafts. Same invoice as `request.methodDetails.invoice`. | Send `Accept-Payment: lightning/charge;q=0` on the initial request if you want the `Payment` header suppressed and only the L402 header returned. ### Redeem with a modern credential Pay the invoice, then build the credential the drafts describe: a JSON object with the challenge you received (echoed exactly, including `request`) and a `payload` holding the preimage as 64 lowercase hex characters. base64url-encode it and send it as a bearer token. ```json { "challenge": { "id": "k9Q3...", "realm": "lightning-enable", "method": "lightning", "intent": "charge", "request": "eyJhbW91bnQiOiIxMDAiLC...", "expires": "2026-09-11T18:30:00Z" }, "payload": { "preimage": "7f8a9b2c...e9f0a" } } ``` ```http GET /l402/proxy/abc123/api/data HTTP/1.1 Authorization: Payment eyJjaGFsbGVuZ2UiOnsiaWQiOiJrOVEz... ``` On success the response includes a receipt you can store or forward: ```http HTTP/1.1 200 OK Payment-Receipt: eyJjaGFsbGVuZ2VJZCI6Ims5UTMuLi4iLCJtZXRob2QiOiJsaWdodG5pbmciLC... Cache-Control: private ``` The receipt decodes to `{"challengeId":"k9Q3...","method":"lightning","reference":"","status":"success","timestamp":"2026-09-11T18:12:04Z"}`. The `reference` is the payment hash, never the preimage. **Single use.** A modern credential is consumed atomically the first time it verifies. A second request with the same token gets a fresh 402. If the server accepted the credential but failed to deliver the upstream response, the consumption is released so you can retry with the same token. Redeem before `expires`. Classic L402 and legacy `Payment` credentials keep their reuse-until-expiry behaviour; nothing about them changed. ### Redeem with a legacy `Payment` credential ```http Authorization: Payment method="lightning", preimage="7f8a9b2c...e9f0a" ``` No macaroon and no challenge echo. The server matches the preimage to a challenge it minted for that resource and price. Reusable until the challenge expires. ### Modern-path errors Failures on the modern path return `402` with `application/problem+json`, a fresh challenge in `WWW-Authenticate`, and `Cache-Control: no-store`. The `type` is `https://paymentauth.org/problems/`: | `type` slug | Meaning | |---|---| | `invalid-challenge` | The echoed challenge doesn't decode, has a currency other than `sat`, or is missing its expiry or payment hash. | | `verification-failed` | The preimage doesn't hash to the challenge's payment hash, the challenge belongs to another merchant, the credential is past `expires`, or it was already redeemed (single use). The `detail` member says which. | | `payment-insufficient` | The challenge amount doesn't match the resource's price. | | `method-unsupported` | Modern credentials are switched off on this server. Fall back to L402. | A malformed bearer token is rejected outright. It is never reinterpreted as a legacy credential. ### Verify credentials as a producer If you mint challenges with the [Producer API](#l402-producer-api), verify each profile with the matching endpoint: - Classic L402 and legacy `Payment` credentials: [`POST /api/l402/challenges/verify`](#verify-l402-token). - Modern bearer credentials: [`POST /api/l402/challenges/verify-credential`](#verify-payment-credential-single-use), which consumes the token and returns a ready-to-serve `Payment-Receipt` value. --- ## L402 Producer API The Producer API lets merchants create L402 challenges programmatically — enabling agent-to-agent commerce where your AI agent charges other agents for access to resources. :::info Requires Agentic Commerce Subscription The Producer API requires a Lightning Enable subscription — **Agentic Commerce** ($49/mo) or **Agentic Commerce — Business** ([contact us](mailto:support@lightningenable.com)). Consumer tools (paying for APIs, accessing L402 resources) are free — no subscription needed. ::: :::tip Agents That Earn This is the supply side of agentic commerce. Your agent creates L402 challenges; other agents pay them. See the [L402 Producer API guide](/products/agentic-commerce/l402-producer-api) for the full walkthrough. ::: ### Create L402 Challenge Create a Lightning invoice + macaroon challenge for a resource. ```http POST /api/l402/challenges ``` #### Headers | Header | Required | Description | |--------|----------|-------------| | `X-API-Key` | Yes | Merchant API key | | `Idempotency-Key` | No | Retry-safe key, max 200 characters. The same key with the same resource and price returns the same challenge for the life of that invoice; a different resource or price under the same key is a `409` while that invoice is still live, and a fresh mint once it has expired. See [Idempotency](/products/agentic-commerce/producer-api-reference#idempotency). | | `X-Idempotency-Key` | No | The spelling this API shipped with. Identical behaviour; `Idempotency-Key` wins if you send both. | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `resource` | string | Yes | Resource identifier (URL, service name, or description) | | `priceSats` | long | Yes | Price in satoshis (minimum 1) | | `description` | string | No | Description shown on the Lightning invoice | | `idempotencyKey` | string | No | Same meaning as the `Idempotency-Key` header, for clients that can't set headers | #### Example ```bash curl -X POST https://api.lightningenable.com/api/l402/challenges \ -H "X-API-Key: YOUR_MERCHANT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "resource": "/api/weather/forecast", "priceSats": 50, "description": "7-day weather forecast" }' ``` #### Response (200 OK) ```json { "invoice": "lnbc500n1p3xyza...", "macaroon": "AgELbGlnaHRuaW5n...", "paymentHash": "abc123def456...", "expiresAt": "2026-03-13T14:30:00Z", "resource": "/api/weather/forecast", "priceSats": 50 } ``` #### Error Responses Returned as `application/problem+json` with a stable `type` URI. See [Error format](/products/agentic-commerce/producer-api-reference#error-format) for the full body and the complete code list. | Status | Description | |--------|-------------| | 400 | Missing required field, invalid price, blank or over-long idempotency key, or no payment provider key on your account | | 401 | Missing or invalid API key | | 402 | A plan cap was reached (price, monthly volume, or distinct endpoints) | | 403 | L402 not enabled on your plan | | 409 | The idempotency key was already used for a different resource or price, or the endpoint was retired | | 503 | The challenge could not be durably recorded, so none was issued — nothing was invoiced | --- ### List Your Challenges List the challenges you have minted, with payment status. Scoped to the account behind your API key. ```http GET /api/l402/challenges?status=paid&since=2026-09-01T00:00:00Z&limit=50&offset=0 ``` | Query parameter | Default | Description | |---|---|---| | `status` | none | `paid`, `unpaid`, or `expired` | | `since` | none | ISO 8601 lower bound on `createdAt` | | `limit` | 50 | Clamped to 1..200 | | `offset` | 0 | Clamped to at least 0 | Returns `{ challenges, total, limit, offset, status, since }`; the unpaged total is also in the `X-Total-Count` header. Each challenge carries `paymentHash`, `resource`, `amountSats`, `status`, `createdAt`, `paidAt`, `expiresAt`, and `idempotencyKey` — never a macaroon or a preimage. ```bash curl "https://api.lightningenable.com/api/l402/challenges?status=paid&limit=25" \ -H "X-API-Key: YOUR_MERCHANT_API_KEY" ``` --- ### Get One Challenge ```http GET /api/l402/challenges/{paymentHash} ``` Returns the same object as one element of the list. A payment hash belonging to another account returns `404`, not `403`. --- ### Verify L402 Token Verify an L402 token (macaroon + preimage) to confirm payment. ```http POST /api/l402/challenges/verify ``` #### Headers | Header | Required | Description | |--------|----------|-------------| | `X-API-Key` | Yes | Merchant API key | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `macaroon` | string | Yes | Base64-encoded macaroon | | `preimage` | string | Yes | Hex-encoded preimage (64 characters) | #### Example ```bash curl -X POST https://api.lightningenable.com/api/l402/challenges/verify \ -H "X-API-Key: YOUR_MERCHANT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "macaroon": "AgELbGlnaHRuaW5n...", "preimage": "7f8a9b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a" }' ``` #### Response (200 OK — valid) ```json { "valid": true, "resource": "/api/weather/forecast", "merchantId": 42, "amountSats": 50, "paymentHash": "abc123def456..." } ``` #### Response (200 OK — invalid) ```json { "valid": false, "error": "Preimage does not match payment hash" } ``` :::note Verifying marks the challenge paid The first successful verification of a credential records the underlying challenge as paid — it starts showing as `status: "paid"` in the listing above, and fires the `l402.challenge.paid` webhook to your callback URL if you have one configured. Later verifications of the same credential do not re-fire it. See [Payment webhooks](/products/agentic-commerce/producer-api-reference#payment-webhooks). ::: --- ### Verify Payment Credential (single-use) Verify a modern `Payment` bearer credential (the base64url JSON token described in [Payment (MPP) credentials](#payment-mpp-credentials)). Verifying consumes the credential: a second call with the same token returns `valid: false`. ```http POST /api/l402/challenges/verify-credential ``` #### Headers | Header | Required | Description | |--------|----------|-------------| | `X-API-Key` | Yes | Merchant API key | #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `credential` | string | Yes | The token exactly as received, with or without the leading `Payment ` scheme word | | `resource` | string | No | Reject unless the challenge was minted for this resource | | `amountSats` | integer | No | Reject unless the challenge was minted for this price | #### Example ```bash curl -X POST https://api.lightningenable.com/api/l402/challenges/verify-credential \ -H "X-API-Key: YOUR_MERCHANT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "credential": "", "resource": "/api/weather/forecast", "amountSats": 50 }' ``` #### Response (200 OK — valid) ```json { "valid": true, "consumed": true, "resource": "/api/weather/forecast", "merchantId": 42, "amountSats": 50, "paymentHash": "abc123def456...", "receipt": "eyJjaGFsbGVuZ2VJZCI6Ims5UTMuLi4iLC..." } ``` Serve `receipt` back to the payer as the `Payment-Receipt` response header on the resource you gate. #### Response (200 OK — invalid) ```json { "valid": false, "consumed": false, "error": "Preimage does not match the challenge payment hash." } ``` `400 mpp_not_supported` means modern credentials are switched off on the server. Like `/verify`, the first successful call marks the challenge paid and fires `l402.challenge.paid`. --- ## Code Examples ### JavaScript L402 Client ```javascript class L402Client { constructor() { this.credentials = new Map(); } async request(url, options = {}) { const credential = this.credentials.get(this.getHost(url)); const headers = { ...options.headers, ...(credential && { 'Authorization': `L402 ${credential}` }) }; const response = await fetch(url, { ...options, headers }); if (response.status === 402) { return this.handlePaymentRequired(url, response, options); } return response; } async handlePaymentRequired(url, response, options) { const { l402 } = await response.json(); // Pay invoice and get preimage const preimage = await this.payInvoice(l402.invoice); // Store credential const credential = `${l402.macaroon}:${preimage}`; this.credentials.set(this.getHost(url), credential); // Retry request return this.request(url, options); } async payInvoice(invoice) { // Integrate with your Lightning wallet // Return the preimage after payment throw new Error('Implement payInvoice()'); } getHost(url) { return new URL(url).host; } } // Usage const client = new L402Client(); const response = await client.request('https://api.example.com/l402/proxy/my-api/data'); ``` ### Python L402 Client ```python import hashlib import requests class L402Client: def __init__(self, pay_invoice_callback): self.credentials = {} self.pay_invoice = pay_invoice_callback def request(self, url, **kwargs): credential = self.credentials.get(self._get_host(url)) if credential: kwargs.setdefault('headers', {}) kwargs['headers']['Authorization'] = f'L402 {credential}' response = requests.request('GET', url, **kwargs) if response.status_code == 402: return self._handle_payment_required(url, response, kwargs) return response def _handle_payment_required(self, url, response, kwargs): data = response.json() l402 = data['l402'] # Pay invoice preimage = self.pay_invoice(l402['invoice']) # Verify preimage matches payment_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest() assert payment_hash == l402['payment_hash'] # Store and retry self.credentials[self._get_host(url)] = f"{l402['macaroon']}:{preimage}" return self.request(url, **kwargs) def _get_host(self, url): from urllib.parse import urlparse return urlparse(url).netloc ``` ### cURL Workflow ```bash #!/bin/bash # Step 1: Get challenge RESPONSE=$(curl -s https://api.example.com/l402/proxy/my-api/data) HTTP_CODE=$(echo "$RESPONSE" | jq -r '.error // empty') if [ "$HTTP_CODE" == "Payment Required" ]; then MACAROON=$(echo "$RESPONSE" | jq -r '.l402.macaroon') INVOICE=$(echo "$RESPONSE" | jq -r '.l402.invoice') echo "Pay this invoice: $INVOICE" echo "Enter preimage after payment:" read PREIMAGE # Step 2: Access with credential curl https://api.example.com/l402/proxy/my-api/data \ -H "Authorization: L402 $MACAROON:$PREIMAGE" fi ``` ## Wallet Integration ### WebLN (Browser) ```javascript async function payL402Invoice(invoice) { if (!window.webln) { throw new Error('WebLN not available'); } await window.webln.enable(); const { preimage } = await window.webln.sendPayment(invoice); return preimage; } ``` ### LND REST API ```javascript async function payWithLND(invoice) { const response = await fetch(`${LND_REST_URL}/v1/channels/transactions`, { method: 'POST', headers: { 'Grpc-Metadata-macaroon': ADMIN_MACAROON }, body: JSON.stringify({ payment_request: invoice }) }); const { payment_preimage } = await response.json(); return Buffer.from(payment_preimage, 'base64').toString('hex'); } ``` ### Core Lightning ```bash # Pay and get preimage lightning-cli pay $INVOICE PREIMAGE=$(lightning-cli listpays bolt11=$INVOICE | jq -r '.pays[0].preimage') ``` ## Best Practices ### Store Credentials Cache L402 credentials for token lifetime: ```javascript const CREDENTIAL_KEY = 'l402_credentials'; function saveCredential(host, credential, expiresAt) { const credentials = JSON.parse(localStorage.getItem(CREDENTIAL_KEY) || '{}'); credentials[host] = { credential, expiresAt }; localStorage.setItem(CREDENTIAL_KEY, JSON.stringify(credentials)); } function getCredential(host) { const credentials = JSON.parse(localStorage.getItem(CREDENTIAL_KEY) || '{}'); const data = credentials[host]; if (data && new Date(data.expiresAt) > new Date()) { return data.credential; } return null; } ``` ### Handle Expired Tokens ```javascript async function request(url) { const response = await fetch(url, { headers: { 'Authorization': `L402 ${getCredential(url)}` } }); if (response.status === 403) { // Token expired, clear and get new one clearCredential(url); return request(url); } return response; } ``` ### Budget Limits Set spending limits: ```javascript class BudgetedL402Client extends L402Client { constructor(maxSatsPerHour) { super(); this.maxSats = maxSatsPerHour; this.spent = 0; this.resetTime = Date.now() + 3600000; } async handlePaymentRequired(url, response, options) { const { l402 } = await response.json(); if (Date.now() > this.resetTime) { this.spent = 0; this.resetTime = Date.now() + 3600000; } if (this.spent + l402.amount_sats > this.maxSats) { throw new Error('Budget exceeded'); } this.spent += l402.amount_sats; return super.handlePaymentRequired(url, response, options); } } ``` ## Next Steps - [Proxy Configuration](/products/agentic-commerce/proxy-configuration) - Set up L402 proxies - [How It Works](/products/agentic-commerce/how-it-works) - Technical deep dive - [API Monetization](/products/agentic-commerce/api-monetization) - Protect your APIs ============================================================================== # Merchant Settings Source: https://docs.lightningenable.com/api-reference/merchant-settings ============================================================================== # Merchant Settings API The Merchant Settings API allows you to manage your own account configuration. All endpoints require authentication with your merchant API key. ## Endpoints | Method | Endpoint | Description | |--------|----------|-------------| | `GET` | `/api/merchant/me` | Get account info and onboarding status | | `GET` | `/api/merchant/l402-status` | Check L402 license status | | `PUT` | `/api/merchant/strike-key` | Update Strike API key (Strike merchants) | | `PUT` | `/api/merchant/opennode-key` | Update OpenNode API key (OpenNode merchants) | | `PUT` | `/api/merchant/payment-provider` | Set the active payment provider (`strike` or `opennode`) | | `PUT` | `/api/merchant/webhook-url` | Update webhook URL and secret | | `GET` | `/api/merchant/subscription` | Get subscription details | | `POST` | `/api/merchant/validate-strike` | Validate Strike API key | | `POST` | `/api/merchant/validate-opennode` | Validate OpenNode API key | | `GET` | `/api/merchant/api-key-info` | Get metadata about your merchant API key | | `POST` | `/api/merchant/regenerate-key` | Rotate your merchant API key | | `GET` | `/api/merchant/quickstart` | Get interactive onboarding guide | :::note Strike API Key Configuration Strike is the recommended default payment provider and is **self-serve** — configure it yourself with `PUT /api/merchant/strike-key`, then confirm it works with `POST /api/merchant/validate-strike`. No support ticket needed. ::: ## Get Account Info Get your merchant account information and onboarding status. ```http GET /api/merchant/me ``` ### Response ```json { "merchantId": 123, "name": "My Company", "email": "api@mycompany.com", "planTier": "individual", "subscriptionStatus": "active", "isActive": true, "createdAt": "2024-01-15T10:30:00Z", "features": { "refundsEnabled": false, "multiCurrencyEnabled": true, "analyticsEnabled": true, "prioritySupport": true, "customBrandingEnabled": false, "maxWebhookEndpoints": 5, "l402Enabled": true }, "onboarding": { "hasPaymentProviderKey": true, "hasWebhookUrl": true, "hasActiveProxy": true, "proxyCount": 2, "isFullyConfigured": true } } ``` ### Example ```bash curl https://api.lightningenable.com/api/merchant/me \ -H "X-API-Key: le_merchant_abc123" ``` ## Check L402 License Status Check if your account has L402 features enabled — useful for dashboards and integrations that want to show a merchant's L402 capability. ```http GET /api/merchant/l402-status ``` ### Response (L402 Enabled) ```json { "l402Enabled": true, "planTier": "l402", "subscriptionStatus": "active", "isActive": true } ``` ### Response (L402 Not Enabled) ```json { "l402Enabled": false, "planTier": "individual", "subscriptionStatus": "active", "isActive": true } ``` `l402Enabled` is a per-account flag, not a plan lookup, so it can read `false` on a tier whose plan includes L402 — as above. All three live tiers include L402, so this body means the flag was never applied to the account, most often on a row created under a tier id that has since been retired. It also reads `false` whenever `isActive` is `false`, whatever the flag says. Contact support to have the flag applied; buying the plan again will not set it. ### Response Fields | Field | Type | Description | |-------|------|-------------| | `l402Enabled` | boolean | Whether L402 features are available | | `planTier` | string | Current plan tier (see mapping table below) | | `subscriptionStatus` | string | Subscription status: `active`, `trialing`, `past_due`, `canceled` | | `isActive` | boolean | Whether the merchant account is active | ### Plan Tier Name Mapping The `planTier` field uses internal code names. There are three, cheapest first: | Internal `planTier` Value | User-Facing Product Name | Price | |---------------------------|--------------------------|-------| | `free` | Free Producer Sandbox | $0 | | `individual` | Agentic Commerce | $49/mo | | `l402` | Agentic Commerce — Business | Contact us | ### Plan Tiers and L402 Support | Plan | Internal Value | Price | L402 Enabled | Trial Eligible | |------|---------------|-------|--------------|----------------| | Free Producer Sandbox | `free` | $0 | ✅ Yes — capped at 3 endpoints, 200 challenges/mo, 1,000 sats per challenge | ❌ No — Free is the floor, not a trial | | Agentic Commerce | `individual` | $49/mo | ✅ Yes | ✅ Yes | | Agentic Commerce — Business | `l402` | Contact us | ✅ Yes | Arranged directly on contact, not via self-serve checkout | :::info `planTier` is normalized — changed September 2026 `planTier` is the **resolved** tier, not the raw stored column. `GET /api/merchant/me`, `GET /api/merchant/l402-status`, and `GET /api/merchant/subscription` all return one of the three values above. This is a contract change. These three fields previously returned the stored column, which meant the same unset account was reported as `pilot` by two of them and `standalone` by the third. They now agree. If your account was created before September 2026 under a tier id that has since been retired — `standard`, `kenticocommerce`, `standalone`, `standaloneapi`, or `pilot` — the field reports the live tier that id maps to, not the id you may have stored: the retired paid ids report as `individual`, and `pilot` reports as `free`. Update any client that string-matches on a retired id, and treat an unrecognized value as Free. ::: ### Example ```bash curl https://api.lightningenable.com/api/merchant/l402-status \ -H "X-API-Key: le_merchant_abc123" ``` :::tip MCP Integration The MCP server's consumer tools (`access_l402_resource`, `pay_l402_challenge`, and the rest of the out-of-the-box set) are free and never call this endpoint — they need only a wallet. Set the `LIGHTNING_ENABLE_API_KEY` environment variable to your merchant API key to unlock the producer and ASA publishing tools. ::: ## Update Strike API Key Configure your Strike API key yourself — Strike is the recommended default and fully self-serve. Saving the key also defaults your account to the Strike provider on first save (it never overwrites an explicit prior provider choice). ```http PUT /api/merchant/strike-key ``` ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `strikeApiKey` | string | Yes | Your Strike API key (needs `partner.receive-request.create` and related scopes) | ### Request ```json { "strikeApiKey": "your-strike-api-key" } ``` ### Example ```bash curl -X PUT https://api.lightningenable.com/api/merchant/strike-key \ -H "X-API-Key: le_merchant_abc123" \ -H "Content-Type: application/json" \ -d '{ "strikeApiKey": "your-strike-api-key" }' ``` After saving, confirm the key works with `POST /api/merchant/validate-strike`. ## Update OpenNode API Key Configure your OpenNode API key. This endpoint applies to merchants using OpenNode as their payment provider. Strike merchants should use `PUT /api/merchant/strike-key` instead (self-serve — no support ticket required). ```http PUT /api/merchant/opennode-key ``` ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `openNodeApiKey` | string | Yes | Your OpenNode API key | :::tip Get Your OpenNode API Key - **Production:** https://app.opennode.com → Settings → API Keys - **Testnet:** https://dev-app.opennode.com → Settings → API Keys ::: ### Request ```json { "openNodeApiKey": "your-opennode-api-key" } ``` ### Response ```json { "success": true, "message": "OpenNode API key updated successfully. You can now create L402 proxies." } ``` ### Example ```bash curl -X PUT https://api.lightningenable.com/api/merchant/opennode-key \ -H "X-API-Key: le_merchant_abc123" \ -H "Content-Type: application/json" \ -d '{ "openNodeApiKey": "your-opennode-api-key" }' ``` ## Update Webhook URL Configure where Lightning Enable should send webhook notifications for payment events. ```http PUT /api/merchant/webhook-url ``` ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `webhookUrl` | string | No | URL to receive webhook notifications | | `webhookSecret` | string | No | Secret for HMAC signature verification | ### Request ```json { "webhookUrl": "https://mycompany.com/webhooks/lightning", "webhookSecret": "my-webhook-signing-secret" } ``` ### Response ```json { "success": true, "message": "Webhook settings updated successfully." } ``` ### Example ```bash curl -X PUT https://api.lightningenable.com/api/merchant/webhook-url \ -H "X-API-Key: le_merchant_abc123" \ -H "Content-Type: application/json" \ -d '{ "webhookUrl": "https://mycompany.com/webhooks/lightning", "webhookSecret": "my-webhook-signing-secret" }' ``` ## Get Subscription Details Get your current subscription plan and limits. ```http GET /api/merchant/subscription ``` ### Response ```json { "planTier": "individual", "planName": "Agentic Commerce", "status": "active", "stripeCustomerId": "cus_abc123", "limits": { "maxMerchants": 1, "maxEnvironments": 2, "maxWebhookEndpoints": 5 } } ``` ### Subscription Status Values | Status | Description | |--------|-------------| | `active` | Subscription is active and paid | | `trialing` | In free trial period (30 days). Full API access. Card required. | | `past_due` | Payment failed, grace period | | `canceled` | Subscription was canceled | ### Example ```bash curl https://api.lightningenable.com/api/merchant/subscription \ -H "X-API-Key: le_merchant_abc123" ``` ## Validate OpenNode API Key Test your OpenNode API key to verify it's configured correctly. ```http POST /api/merchant/validate-opennode ``` ### Response (Valid Key) ```json { "isValid": true, "message": "OpenNode API key is valid and working." } ``` ### Response (Invalid Key) ```json { "isValid": false, "message": "OpenNode API key validation failed: 401" } ``` ### Response (No Key Configured) ```json { "isValid": false, "message": "No OpenNode API key configured. Use PUT /api/merchant/opennode-key to add one." } ``` ### Example ```bash curl -X POST https://api.lightningenable.com/api/merchant/validate-opennode \ -H "X-API-Key: le_merchant_abc123" ``` ## Validate Strike API Key Test your Strike API key to verify it's configured correctly. ```http POST /api/merchant/validate-strike ``` ### Response (No Key Configured) ```json { "isValid": false, "message": "No Strike API key configured. Use PUT /api/merchant/strike-key to add one." } ``` ### Example ```bash curl -X POST https://api.lightningenable.com/api/merchant/validate-strike \ -H "X-API-Key: le_merchant_abc123" ``` ## Get Quickstart Guide Get an interactive onboarding guide that tracks your setup progress. ```http GET /api/merchant/quickstart ``` ### Response ```json { "merchantId": 123, "merchantName": "My Company", "completedSteps": 3, "totalSteps": 6, "requiredStepsCompleted": 3, "requiredStepsTotal": 4, "isReadyForProduction": true, "steps": [ { "stepNumber": 1, "title": "Configure Payment Provider API Key", "description": "Add your payment provider API key (Strike or OpenNode) so Lightning Enable can create invoices on your behalf.", "endpoint": "PUT /api/merchant/opennode-key", "exampleRequest": "{ \"openNodeApiKey\": \"your-opennode-api-key\" }", "isCompleted": true, "isRequired": true }, { "stepNumber": 2, "title": "Validate Payment Provider Key", "description": "Verify your payment provider API key is working correctly.", "endpoint": "POST /api/merchant/validate-opennode", "exampleRequest": null, "isCompleted": true, "isRequired": true }, { "stepNumber": 3, "title": "Create Your First Proxy", "description": "Create an L402 proxy configuration pointing to your API.", "endpoint": "POST /api/proxy", "exampleRequest": "{ \"name\": \"My API\", \"targetBaseUrl\": \"https://api.yourcompany.com/v1\", \"defaultPriceSats\": 100 }", "isCompleted": true, "isRequired": true } ] } ``` This endpoint is useful for building onboarding UIs that guide users through the setup process. ### Example ```bash curl https://api.lightningenable.com/api/merchant/quickstart \ -H "X-API-Key: le_merchant_abc123" ``` ## Error Responses ### 401 Unauthorized ```json { "error": "Authentication required" } ``` ### 400 Bad Request ```json { "error": "Payment provider API key is required" } ``` ```json { "error": "Invalid webhook URL format" } ``` ### 404 Not Found ```json { "error": "Merchant not found" } ``` ## Next Steps - [Authentication](/api-reference/authentication) - API key management - [L402 Protocol](/api-reference/l402) - Set up L402 proxy for API monetization - [Webhooks](/api-reference/webhooks) - Configure webhook notifications ============================================================================== # API Overview Source: https://docs.lightningenable.com/api-reference/overview ============================================================================== # API Reference The Lightning Enable API is a RESTful web service for integrating Bitcoin Lightning payments into your platform. ## Base URL ``` https://api.lightningenable.com ``` ## Authentication All API requests require authentication via the `X-API-Key` header: ```bash curl -X GET https://api.lightningenable.com/api/merchant/me \ -H "X-API-Key: YOUR_API_KEY" ``` See [Authentication](/api-reference/authentication) for details. ## API Endpoints ### Payments | Method | Endpoint | Description | |--------|----------|-------------| | `POST` | `/api/payments` | Create payment invoice | | `GET` | `/api/payments/{invoiceId}/status` | **Public** payment status (no API key — safe for browser polling) | | `GET` | `/api/payments/{invoiceId}` | Get payment by ID | | `GET` | `/api/payments/order/{orderId}` | Get payment by order ID | | `POST` | `/api/payments/{invoiceId}/sync` | Sync status from your payment provider | ### Refunds | Method | Endpoint | Description | |--------|----------|-------------| | `POST` | `/api/refunds` | Create refund | | `GET` | `/api/refunds/{refundId}` | Get refund status | | `GET` | `/api/refunds` | List all refunds | | `GET` | `/api/refunds/invoice/{invoiceId}` | Get refunds for invoice | | `POST` | `/api/refunds/{refundId}/sync` | Sync refund status from your payment provider | ### L402 Protocol | Method | Endpoint | Description | |--------|----------|-------------| | `POST` | `/api/l402/challenges` | Create an L402 challenge (producer API) | | `POST` | `/api/l402/challenges/verify` | Verify an L402 token (producer API) | | `GET` | `/api/l402/pricing` | Get L402 endpoint pricing | | `GET` | `/api/l402/status` | Check L402 auth status | | `*` | `/l402/proxy/{proxyId}/*` | L402-protected proxy | See the [Producer API Reference](/products/agentic-commerce/producer-api-reference) for the challenge/verify contract. ### Webhooks | Method | Endpoint | Description | |--------|----------|-------------| | `POST` | `/api/webhooks/opennode` | OpenNode webhook receiver | | `POST` | `/api/webhooks/strike` | Strike webhook receiver | These receive events **from your payment provider** — your own notifications arrive at the callback URL you configure. See [Webhooks](/api-reference/webhooks). ### Merchant Settings (Self-Service) | Method | Endpoint | Description | |--------|----------|-------------| | `GET` | `/api/merchant/me` | Get account info and onboarding status | | `GET` | `/api/merchant/subscription` | Get subscription details | | `GET` | `/api/merchant/l402-status` | Get L402 feature status | | `GET` | `/api/merchant/api-key-info` | API key metadata (created/last-rotated) | | `GET` | `/api/merchant/quickstart` | Get onboarding guide | | `POST` | `/api/merchant/regenerate-key` | Regenerate your API key | | `PUT` | `/api/merchant/payment-provider` | Switch payment provider (Strike/OpenNode) | | `PUT` | `/api/merchant/strike-key` | Update Strike API key | | `PUT` | `/api/merchant/opennode-key` | Update OpenNode API key | | `PUT` | `/api/merchant/webhook-url` | Update webhook URL | | `POST` | `/api/merchant/validate-strike` | Validate Strike API key | | `POST` | `/api/merchant/validate-opennode` | Validate OpenNode API key | Full request/response schemas: [Merchant Settings](/api-reference/merchant-settings). ### Health Check | Method | Endpoint | Description | |--------|----------|-------------| | `GET` | `/health` | Structured health check (public, no auth required) | ## Request Format ### Headers | Header | Required | Description | |--------|----------|-------------| | `X-API-Key` | Yes | Your merchant API key | | `Content-Type` | Yes (POST/PUT) | `application/json` | | `X-Idempotency-Key` | No | Prevents duplicate payments/refunds (UUID recommended) | | `X-Correlation-Id` | No | Request tracing ID (auto-generated if not provided) | See [Request Headers](/api-reference/headers) for full details on idempotency, correlation IDs, and API versioning. ### Request Body POST and PUT requests accept JSON bodies: ```json { "orderId": "ORDER-12345", "amount": 99.99, "currency": "USD" } ``` ## Response Format ### Success Response ```json { "invoiceId": "1042", "status": "unpaid", "amount": 99.99, "currency": "USD" } ``` ### Error Response Errors carry an `error` field (sometimes with a `message`); there is no machine-readable `code` field — dispatch on the HTTP status: ```json { "error": "Invoice already exists for OrderId ORDER-12345" } ``` Model-binding validation failures return the standard ASP.NET validation problem shape (400 with an `errors` dictionary). See [Errors](/api-reference/errors). ## HTTP Status Codes | Code | Description | |------|-------------| | `200` | Success | | `201` | Created | | `400` | Bad Request - Invalid parameters | | `401` | Unauthorized - Invalid API key | | `402` | Payment Required - L402 payment needed | | `403` | Forbidden - Access denied | | `404` | Not Found - Resource doesn't exist | | `429` | Too Many Requests - Rate limited | | `500` | Server Error | ## Pagination List endpoints support pagination using `skip` and `take` parameters: ```bash GET /api/refunds?skip=0&take=20 ``` | Parameter | Default | Description | |-----------|---------|-------------| | `skip` | 0 | Number of records to skip | | `take` | 50 | Number of records to return (max 100) | Example - get second page of 20 results: ```bash GET /api/refunds?skip=20&take=20 ``` ## Rate Limiting | Policy | Limit | Applied To | |--------|-------|------------| | Global | 100/min | All authenticated requests (per API key) | | Read | 200/min | GET operations | | Payment Create | 10/min | POST /api/payments, POST /api/refunds | | Admin | 30/min | Internal admin endpoints | See [Rate Limiting](/api-reference/rate-limiting) for details. ## SDKs and Libraries Official L402 client libraries (auto-paying HTTP clients): - **.NET / C#** — [`L402Requests`](https://www.nuget.org/packages/L402Requests) on NuGet - **JavaScript / TypeScript** — [`l402-requests`](https://www.npmjs.com/package/l402-requests) on npm - **Python** — [`l402-requests`](https://pypi.org/project/l402-requests/) on PyPI Official Agent SDKs (higher-level agent commerce primitives): - **.NET / C#** — [`LightningEnable.AgentSdk`](https://www.nuget.org/packages/LightningEnable.AgentSdk) on NuGet - **JavaScript / TypeScript** — [`le-agent-sdk`](https://www.npmjs.com/package/le-agent-sdk) on npm - **Python** — [`le-agent-sdk`](https://pypi.org/project/le-agent-sdk/) on PyPI For MCP-server integration (Claude Desktop, Claude Code, etc.), see the [Lightning Enable MCP](https://github.com/refined-element/lightning-enable-mcp) repository. ## Quick Examples ### Create Payment ```bash curl -X POST https://api.lightningenable.com/api/payments \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "orderId": "ORDER-12345", "amount": 49.99, "currency": "USD", "description": "Premium Subscription" }' ``` ### Check Payment Status ```bash curl https://api.lightningenable.com/api/payments/1042 \ -H "X-API-Key: YOUR_API_KEY" ``` ### Create Refund ```bash curl -X POST https://api.lightningenable.com/api/refunds \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "invoiceId": "1042", "refundAddress": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", "reason": "Customer request" }' ``` ## Health Check Endpoint The `/health` endpoint returns structured JSON describing the status of the API and its dependencies. It is **public** and does not require authentication, making it suitable for external monitoring and infrastructure health probes. ### Request ```bash curl https://api.lightningenable.com/health ``` ### Response ```json { "status": "Healthy", "totalDuration": 42.15, "checks": [ { "name": "database", "status": "Healthy", "duration": 38.72, "description": null, "exception": null, "tags": ["db", "sql"] } ] } ``` ### Response Fields | Field | Type | Description | |-------|------|-------------| | `status` | string | Overall health: `Healthy`, `Degraded`, or `Unhealthy` | | `totalDuration` | number | Total time to run all checks (milliseconds) | | `checks` | array | Individual health check results | | `checks[].name` | string | Name of the check (e.g., `database`) | | `checks[].status` | string | Check result: `Healthy`, `Degraded`, or `Unhealthy` | | `checks[].duration` | number | Time for this check (milliseconds) | | `checks[].description` | string\|null | Optional description from the check | | `checks[].exception` | string\|null | Error message if the check failed | | `checks[].tags` | array | Tags for categorizing checks (e.g., `["db", "sql"]`) | ### HTTP Status Codes | Code | Meaning | |------|---------| | `200` | All checks passed (`Healthy`) | | `503` | One or more checks failed (`Unhealthy`) | ### Current Checks | Check | Tags | What It Verifies | |-------|------|-----------------| | `database` | `db`, `sql` | SQL Server connectivity via EF Core `CanConnectAsync` | ### Unhealthy Response Example When the database is unreachable, the endpoint returns HTTP 503: ```json { "status": "Unhealthy", "totalDuration": 5023.41, "checks": [ { "name": "database", "status": "Unhealthy", "duration": 5001.88, "description": null, "exception": "A network-related or instance-specific error occurred while establishing a connection to SQL Server.", "tags": ["db", "sql"] } ] } ``` ### Using with Monitoring Tools **Azure App Service Health Probes:** Configure in the Azure portal under **Monitoring > Health check**: - Path: `/health` - The probe will automatically mark the instance as unhealthy after consecutive failures. **UptimeRobot / Pingdom / External Monitors:** Point your uptime monitor at: ``` https://api.lightningenable.com/health ``` - Alert on HTTP status code other than `200` - Recommended check interval: 1 minute - Parse the JSON response to alert on specific check failures (e.g., `checks[0].status != "Healthy"`) **curl Quick Check:** ```bash # Check overall status curl -s https://api.lightningenable.com/health | jq '.status' # Check database specifically curl -s https://api.lightningenable.com/health | jq '.checks[] | select(.name == "database") | .status' ``` ## Next Steps - [Authentication](/api-reference/authentication) - API key management - [Request Headers](/api-reference/headers) - Idempotency, correlation IDs, and API versioning - [Payments](/api-reference/payments) - Create and manage payments - [Webhooks](/api-reference/webhooks) - Real-time notifications - [Errors](/api-reference/errors) - Error handling ============================================================================== # Payments API Source: https://docs.lightningenable.com/api-reference/payments ============================================================================== # Payments API Create Lightning invoices and manage payment status. ## Create Payment Create a new Lightning invoice for a customer payment. ```http POST /api/payments ``` ### Request Headers | Header | Required | Description | |--------|----------|-------------| | `X-API-Key` | Yes | Your merchant API key | | `Content-Type` | Yes | `application/json` | ### Request Body ```json { "orderId": "ORDER-12345", "amount": 49.99, "currency": "USD", "description": "Premium Subscription", "customerEmail": "customer@example.com", "metadata": { "productId": "prod_123", "userId": "user_456" } } ``` ### Parameters | Field | Type | Required | Description | |-------|------|----------|-------------| | `orderId` | string | Yes | Your unique order identifier (1–100 chars) | | `amount` | decimal | Yes | Payment amount in `currency`. **`0` creates a demo invoice** that auto-confirms without real payment | | `currency` | string | Yes | Uppercase 3-letter code (`USD`, `EUR`, `GBP`, `BTC`) — anything else fails validation | | `description` | string | No | Payment description (max 500 chars) | | `customerEmail` | string | No | Customer email for receipt | | `customerName` | string | No | Customer name (max 200 chars) | | `successUrl` | string | No | URL to send the customer to after payment | | `metadata` | object | No | Custom key-value data — **string values only** | ### Response ```json { "invoiceId": "1042", "status": "unpaid", "amount": 49.99, "currency": "USD", "lightningInvoice": "lnbc1250000n1pnxyz...", "paymentHash": "a1b2c3d4e5f6...", "onchainAddress": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", "hostedCheckoutUrl": "https://checkout.opennode.com/abc123", "payUrl": "https://api.lightningenable.com/pay/YOUR_PAYMENT_TOKEN", "providerChargeId": "charge_abc123", "openNodeChargeId": "charge_abc123", "createdAt": "2026-07-03T12:00:00Z", "expiresAt": "2026-07-03T13:00:00Z" } ``` | Field | Notes | |-------|-------| | `invoiceId` | Lightning Enable invoice ID — a numeric string (e.g. `"1042"`). Use it for status lookups | | `lightningInvoice` | BOLT11 invoice to display / encode as a QR code | | `paymentHash` | The invoice's payment hash — use this instead of parsing the BOLT11 yourself | | `onchainAddress` | On-chain fallback address, when the provider supplies one | | `hostedCheckoutUrl` | Provider-hosted checkout page — **OpenNode only**; empty for Strike merchants | | `payUrl` | Lightning Enable's own hosted checkout page for this invoice. **Treat it as a secret** — see below | | `expiresAt` | When the invoice expires — always honor this field (see [Invoice Expiration](#invoice-expiration)) | :::caution `payUrl` is a payment link, not a public URL The path segment after `/pay/` is a random per-invoice token, and holding it is what authorizes the checkout page: anyone with the URL can see that invoice's amount, description, merchant name and BOLT11 invoice. Send it to the buyer, and keep it out of logs, referrers and crawlable pages — the same care you would give a payment link from any processor. It cannot be derived from `invoiceId`, and `/pay/1042` does not resolve. If you lose the URL, read `payUrl` back from `GET /api/payments/{invoiceId}` with your API key. ::: :::note Provider Charge IDs `providerChargeId` is the provider-agnostic charge identifier that works with both Strike and OpenNode. `openNodeChargeId` is retained for backward compatibility and will always be populated alongside `providerChargeId`. ::: ### Example ```bash curl -X POST https://api.lightningenable.com/api/payments \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "orderId": "ORDER-12345", "amount": 49.99, "currency": "USD", "description": "Premium Subscription" }' ``` ## Get Payment by Invoice ID Retrieve payment details by Lightning Enable invoice ID. ```http GET /api/payments/{invoiceId} ``` ### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `invoiceId` | string | Lightning Enable invoice ID | ### Response ```json { "invoiceId": "1042", "orderId": "ORDER-12345", "providerChargeId": "charge_abc123", "openNodeChargeId": "charge_abc123", "status": "paid", "amount": 49.99, "currency": "USD", "lightningInvoice": "lnbc1250000n1pnxyz...", "onchainAddress": "bc1qxy2...", "hostedCheckoutUrl": "https://checkout.opennode.com/abc123", "payUrl": "https://api.lightningenable.com/pay/YOUR_PAYMENT_TOKEN", "createdAt": "2026-07-03T12:00:00Z", "paidAt": "2026-07-03T12:05:00Z", "expiresAt": "2026-07-03T13:00:00Z" } ``` ### Example ```bash curl https://api.lightningenable.com/api/payments/1042 \ -H "X-API-Key: YOUR_API_KEY" ``` ## Public Payment Status (browser polling) ```http GET /api/payments/{invoiceId}/status ``` **No API key required.** Returns only `{ "status": "..." }`, so it is safe to call from client-side code — this is the endpoint your checkout page should poll instead of embedding your API key in the browser: ```json { "status": "paid" } ``` ```javascript // Browser-safe polling const { status } = await (await fetch( `https://api.lightningenable.com/api/payments/${invoiceId}/status` )).json(); ``` Fulfillment decisions belong on your server — use webhooks or the authenticated endpoints above for the authoritative record. ## Get Payment by Order ID Retrieve payment details by your order ID. ```http GET /api/payments/order/{orderId} ``` ### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `orderId` | string | Your order identifier | ### Response Same as Get Payment by Invoice ID. ### Example ```bash curl https://api.lightningenable.com/api/payments/order/ORDER-12345 \ -H "X-API-Key: YOUR_API_KEY" ``` ## Sync Payment Status Force sync payment status from the payment provider. Useful if webhook was delayed. ```http POST /api/payments/{invoiceId}/sync ``` ### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `invoiceId` | string | Lightning Enable invoice ID | ### Response Returns updated payment object with current status. ### Example ```bash curl -X POST https://api.lightningenable.com/api/payments/1042/sync \ -H "X-API-Key: YOUR_API_KEY" ``` ## Payment Statuses | Status | Description | |--------|-------------| | `unpaid` | Invoice created, awaiting payment | | `processing` | Payment detected, confirming | | `paid` | Payment confirmed | | `expired` | Invoice expired without payment | | `refunded` | Payment was refunded | ## Payment Flow ``` 1. Create Payment POST /api/payments Returns: invoiceId, lightningInvoice 2. Display to Customer Show QR code or invoice string Customer pays with Lightning wallet 3. Payment Confirmed Webhook notification sent Or poll GET /api/payments/{invoiceId} 4. Fulfill Order When status = "paid", deliver goods/services ``` ## Supported Currencies | Currency | Code | Notes | |----------|------|-------| | US Dollar | `USD` | Converted to BTC at current rate | | Euro | `EUR` | Converted to BTC at current rate | | British Pound | `GBP` | Converted to BTC at current rate | | Bitcoin | `BTC` | Direct amount as a decimal with up to 8 places — for satoshi precision, use BTC (e.g. `0.00062500` = 62,500 sats) | Currency codes must be **uppercase 3-letter** values — there is no `sats` currency code; lowercase or 4-letter values fail validation with a 400. ## Invoice Expiration Expiration is **provider-dependent** — always read the `expiresAt` field from the response instead of assuming a window: - **Strike** (recommended default): ~60 minutes - **OpenNode**: the provider's invoice TTL - **Demo invoices** (`amount: 0`): 10 minutes After expiration an invoice cannot be paid — create a new payment. ## Error Responses Errors are plain JSON with an `error` field (and no machine-readable `code` field — dispatch on HTTP status): ```json // 400 — business validation (e.g. duplicate order) { "error": "Invoice already exists for OrderId ORDER-12345" } // 404 — unknown invoice / wrong merchant { "error": "Invoice not found" } ``` Model-binding failures (missing `orderId`, malformed `currency`, negative `amount`) return the standard ASP.NET validation problem shape — a 400 with an `errors` dictionary keyed by field name. See [Errors](/api-reference/errors) for the full contract. ## Code Examples ### JavaScript ```javascript async function createPayment(orderId, amount, currency) { const response = await fetch('https://api.lightningenable.com/api/payments', { method: 'POST', headers: { 'X-API-Key': process.env.LIGHTNING_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ orderId, amount, currency }) }); if (!response.ok) { throw new Error(`Payment failed: ${response.statusText}`); } return response.json(); } // Usage const payment = await createPayment('ORDER-123', 49.99, 'USD'); console.log('Lightning Invoice:', payment.lightningInvoice); ``` ### C# ```csharp public async Task CreatePaymentAsync( string orderId, decimal amount, string currency) { var request = new { orderId, amount, currency }; var response = await _httpClient.PostAsJsonAsync("/api/payments", request); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); } ``` ### Python ```python import requests def create_payment(order_id, amount, currency): response = requests.post( 'https://api.lightningenable.com/api/payments', headers={ 'X-API-Key': os.environ['LIGHTNING_API_KEY'], 'Content-Type': 'application/json' }, json={ 'orderId': order_id, 'amount': amount, 'currency': currency } ) response.raise_for_status() return response.json() ``` ## Next Steps - [Webhooks](/api-reference/webhooks) - Real-time payment notifications - [Errors](/api-reference/errors) - Error handling ============================================================================== # Rate Limiting Source: https://docs.lightningenable.com/api-reference/rate-limiting ============================================================================== # Rate Limiting Lightning Enable applies rate limits to ensure fair usage and system stability. ## Rate Limits ### Default Limits | Policy | Limit | Window | Applied To | |--------|-------|--------|------------| | **Global** | 100 requests | 1 minute | All requests (per API key when authenticated, per IP when anonymous) | | **Read** | 200 requests | 1 minute | GET operations on payments, refunds, merchant settings | | **Payment Create** | 10 requests | 1 minute | POST /api/payments, POST /api/refunds, /api/checkout/* | | **Write** | 20 requests | 1 minute | Merchant self-service writes (e.g., POST /api/merchant/regenerate-key) | | **Checkout Create** | 5 requests | 1 minute | Stripe checkout session creation | | **Admin** | 30 requests | 1 minute | Internal admin endpoints | | **Webhook** | 100 requests | 1 minute (sliding window) | `/api/webhooks/*` | | **Magic Link** | 3 requests | 15 minutes | Magic link email requests (per IP) | :::note Rate limits are designed to prevent abuse while allowing normal operations. The global limiter applies per API key for authenticated requests, or per IP address for anonymous requests. ::: ### Webhook Rate Limiting Webhook endpoints use a **sliding window** rate limiter instead of a fixed window. This provides smoother rate limiting by avoiding burst-at-boundary issues that can occur with fixed windows. The sliding window is divided into 4 segments per minute, so the limit is evaluated more granularly than a simple 100-per-minute counter. This prevents scenarios where a burst of 100 requests at the end of one window and 100 at the start of the next would be allowed. ``` Fixed window: |--- 100 allowed ---|--- 100 allowed ---| ^ boundary allows burst of 200 Sliding window: Smoothly tracks usage across 4 segments No burst-at-boundary problem ``` Webhook rate limiting applies to: - `POST /api/webhooks/opennode` -- OpenNode payment webhooks - `POST /api/webhooks/strike` -- Strike payment webhooks ## Rate Limit Exceeded When you exceed a rate limit, the API returns `429 Too Many Requests` with the wait time in the **JSON body**: ```http HTTP/1.1 429 Too Many Requests Content-Type: application/json { "error": "Too many requests", "message": "Rate limit exceeded. Please try again later.", "retryAfter": 60 } ``` | Body field | Type | Description | |------------|------|-------------| | `error` | string | Always `"Too many requests"` for the general rate limiter | | `message` | string | Human-readable description | | `retryAfter` | number | Seconds to wait before retrying | :::info No rate-limit headers The API does **not** emit `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, or `Retry-After` headers on rate-limited (or successful) responses. Do not write client code that parses these headers — the only rate-limit signal is the 429 status plus the `retryAfter` field in the JSON body. (The one exception: the [failed-authentication throttle](#failed-authentication-throttling) below sets a `Retry-After` header on its 429s.) ::: ## Failed-Authentication Throttling Separately from the request rate limiter, Lightning Enable throttles **failed authentication attempts per IP address**: more than **20 failures within a 60-second fixed window** blocks further authenticated requests from that IP until the window expires. ```http HTTP/1.1 429 Too Many Requests Retry-After: 42 Content-Type: application/json { "error": "Too many failed authentication attempts", "message": "Slow down and try again in 42 seconds" } ``` Key differences from the general rate limiter: | | General rate limiter | Auth-failure throttle | |---|---|---| | Trigger | Too many requests | Too many *failed* authentications (missing/invalid API key) | | Scope | Per API key (or per IP anonymous) | Per IP, regardless of which keys were tried | | `error` string | `"Too many requests"` | `"Too many failed authentication attempts"` | | Wait signal | `retryAfter` in JSON body | `Retry-After` response header (and the message text) | | Correct response | Back off and retry | **Fix your API key** — do not retry | If you hit this throttle, your integration is sending a wrong or stale API key. Retrying with the same key keeps recording failures and re-arms the block. Verify your key at **Dashboard → Settings** and update your configuration before retrying. See [Authentication](/api-reference/authentication#failed-authentication-throttling). ## Handling Rate Limits ### Handle 429 Errors Read the wait time from the JSON body's `retryAfter` field: ```javascript async function requestWithRetry(url, options, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(url, options); if (response.status === 429) { const body = await response.json(); if (body.error === 'Too many failed authentication attempts') { // Auth throttle — retrying won't help until the key is fixed throw new Error('Authentication throttled. Check your API key.'); } const retryAfter = body.retryAfter || 60; console.log(`Rate limited. Waiting ${retryAfter} seconds...`); await sleep(retryAfter * 1000); continue; } return response; } throw new Error('Max retries exceeded'); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } ``` ### C# Implementation ```csharp public class RateLimitedHttpClient { private readonly HttpClient _client; public RateLimitedHttpClient(HttpClient client) => _client = client; public async Task SendAsync( HttpRequestMessage request, int maxRetries = 3) { for (var attempt = 0; attempt < maxRetries; attempt++) { var response = await _client.SendAsync(request); if (response.StatusCode != HttpStatusCode.TooManyRequests) { return response; } // The wait time is in the JSON body, not a header var body = await response.Content .ReadFromJsonAsync(); if (body?.Error == "Too many failed authentication attempts") { throw new InvalidOperationException( "Authentication throttled. Check your API key."); } var delay = TimeSpan.FromSeconds(body?.RetryAfter ?? 60); await Task.Delay(delay); } throw new InvalidOperationException("Max retries exceeded"); } private sealed record RateLimitResponse( string? Error, string? Message, double? RetryAfter); } ``` ### Python Implementation ```python import time import requests class RateLimitedClient: def __init__(self, api_key): self.api_key = api_key def request(self, method, url, max_retries=3, **kwargs): headers = kwargs.pop('headers', {}) headers['X-API-Key'] = self.api_key for _ in range(max_retries): response = requests.request(method, url, headers=headers, **kwargs) if response.status_code != 429: return response # The wait time is in the JSON body, not a header body = response.json() if body.get('error') == 'Too many failed authentication attempts': raise RuntimeError('Authentication throttled. Check your API key.') retry_after = body.get('retryAfter', 60) time.sleep(retry_after) raise RuntimeError('Max retries exceeded') ``` ## Best Practices ### 1. Implement Exponential Backoff ```javascript async function exponentialBackoff(fn, maxRetries = 5) { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { if (error.status !== 429 || i === maxRetries - 1) { throw error; } // Prefer the server-provided retryAfter; fall back to backoff const base = error.retryAfter ? error.retryAfter * 1000 : Math.min(1000 * Math.pow(2, i), 60000); const jitter = Math.random() * 1000; await sleep(base + jitter); } } } ``` ### 2. Use Request Queuing ```javascript class RequestQueue { constructor(maxConcurrent = 10) { this.queue = []; this.running = 0; this.maxConcurrent = maxConcurrent; } async add(fn) { return new Promise((resolve, reject) => { this.queue.push({ fn, resolve, reject }); this.process(); }); } async process() { if (this.running >= this.maxConcurrent || this.queue.length === 0) { return; } this.running++; const { fn, resolve, reject } = this.queue.shift(); try { const result = await fn(); resolve(result); } catch (error) { reject(error); } finally { this.running--; this.process(); } } } // Usage const queue = new RequestQueue(5); const results = await Promise.all( paymentIds.map(id => queue.add(() => getPayment(id)) ) ); ``` ### 3. Cache Responses ```javascript const cache = new Map(); const CACHE_TTL = 60000; // 1 minute async function getCachedRate(currency) { const cacheKey = `rate:${currency}`; const cached = cache.get(cacheKey); if (cached && Date.now() - cached.timestamp < CACHE_TTL) { return cached.data; } const data = await fetchRate(currency); cache.set(cacheKey, { data, timestamp: Date.now() }); return data; } ``` ### 4. Batch Requests Instead of individual requests: ```javascript // Bad - 100 API calls for (const orderId of orderIds) { const payment = await getPayment(orderId); } // Good - Use webhooks or batch endpoints // Payments are pushed via webhook, no polling needed ``` ### 5. Use Webhooks Don't poll for payment status. Use webhooks instead: ```javascript // Bad - Polling every 5 seconds setInterval(async () => { const status = await getPaymentStatus(invoiceId); if (status === 'paid') { fulfillOrder(); } }, 5000); // Good - Webhook notification app.post('/webhooks/lightning', (req, res) => { if (req.body.event === 'payment.completed') { fulfillOrder(req.body.data.orderId); } res.status(200).send('OK'); }); ``` ## Rate Limit by Endpoint ### Payment Endpoints | Endpoint | Policy | Limit | |----------|--------|-------| | `POST /api/payments` | payment-create | 10/min | | `GET /api/payments/{id}` | read | 200/min | | `GET /api/payments/order/{orderId}` | read | 200/min | | `POST /api/payments/{id}/sync` | read | 200/min | ### Refund Endpoints | Endpoint | Policy | Limit | |----------|--------|-------| | `POST /api/refunds` | payment-create | 10/min | | `GET /api/refunds` | read | 200/min | | `GET /api/refunds/{id}` | read | 200/min | ### Merchant Self-Service Endpoints | Endpoint | Policy | Limit | |----------|--------|-------| | `GET /api/merchant/me` | read | 200/min | | `PUT /api/merchant/opennode-key` | read | 200/min | | `PUT /api/merchant/webhook-url` | read | 200/min | | `POST /api/merchant/regenerate-key` | write | 20/min | ### Webhook Endpoints | Endpoint | Policy | Limit | |----------|--------|-------| | `POST /api/webhooks/opennode` | webhook (sliding window) | 100/min | | `POST /api/webhooks/strike` | webhook (sliding window) | 100/min | ### L402 Endpoints | Endpoint | Policy | Limit | |----------|--------|-------| | `GET /api/l402/pricing` | read | 200/min | | `GET /api/l402/status` | read | 200/min | | `/l402/proxy/*` | global | 100/min | ## Enterprise Options Need higher rate limits? Contact us for enterprise plans with: - Custom rate limits based on your needs - Dedicated infrastructure - Priority support - Enhanced support Contact: enterprise@lightningenable.com ## Next Steps - [Errors](/api-reference/errors) - Error handling - [Authentication](/api-reference/authentication) - API key setup - [Webhooks](/api-reference/webhooks) - Avoid polling with webhooks ============================================================================== # Settlements API Source: https://docs.lightningenable.com/api-reference/settlements ============================================================================== # Settlements API :::info Settlements = Completed Payments In Lightning Enable, settlements are completed payments. The same API endpoints handle both. See the **[Payments API](./payments.md)** for the complete reference. ::: When a Lightning payment is confirmed, it becomes a settlement. Your payment provider (Strike or OpenNode) handles the settlement process automatically. ## Key Endpoints All payment endpoints in the [Payments API](./payments.md) apply to settlements: - `POST /api/payments` — Create a payment (which settles automatically on confirmation) - `GET /api/payments/{invoiceId}` — Check payment/settlement status - `POST /api/payments/{invoiceId}/sync` — Force sync status from your payment provider ## Settlement Webhooks You'll receive a webhook notification when a payment settles. There is no event envelope and no `payment.completed` event type — Lightning Enable forwards **one flat JSON object per payment event** and you route on its `status` field (treat `status: "paid"` as the settlement/fulfillment trigger). See [Webhooks](./webhooks.md) for the exact payload shapes and signature verification. ============================================================================== # Webhooks Source: https://docs.lightningenable.com/api-reference/webhooks ============================================================================== # Webhooks Receive real-time notifications when payment events occur. ## Overview Webhooks notify your application when: - A payment is completed - A payment expires or its status changes Instead of polling the API, webhooks push events to your server instantly. ## How It Works ``` 1. Customer pays Lightning invoice 2. Your payment provider (Strike or OpenNode) confirms payment 3. The provider sends a webhook to Lightning Enable (POST /api/webhooks/strike or POST /api/webhooks/opennode) 4. Lightning Enable forwards a signed webhook to your callback URL 5. Your server processes the event ``` ## Webhook Payload Lightning Enable forwards **one flat JSON object per event** to your callback URL. There is no envelope. Two families of event arrive on the same URL, with the same signature scheme, and they are told apart by which discriminator is present: | Family | Discriminator | Sent when | |---|---|---| | **Payment events** | `status` (no `event` field) | A provider invoice from `POST /api/payments` changes state | | **L402 producer events** | `event` (no `status` field) | A challenge you minted is proven paid — see [L402 producer events](#l402-producer-events) | Check for `event` first and fall through to `status`, so a new producer event never lands in your payment-status switch. The payment payload's shape depends on which payment provider your merchant account uses. **OpenNode merchants:** ```json { "invoiceId": "1042", "orderId": "ORDER-12345", "status": "paid", "amount": 49.99, "currency": "USD", "openNodeChargeId": "abc123-def456-...", "paidAt": "2026-07-03T12:05:00Z", "metadata": "{\"customerId\":\"cust_42\"}" } ``` **Strike merchants:** ```json { "invoiceId": "1042", "orderId": "ORDER-12345", "status": "paid", "amount": 49.99, "currency": "USD", "providerChargeId": "8f6c3f5e-1c2d-...", "provider": "strike", "paidAt": "2026-07-03T12:05:00Z", "metadata": null } ``` | Field | Description | |-------|-------------| | `invoiceId` | Lightning Enable invoice ID (numeric string) — the same `invoiceId` returned by `POST /api/payments` | | `orderId` | Your order ID from payment creation | | `status` | Payment status string. Treat `paid` as the fulfillment trigger. Other values include `processing`, `expired`, and (OpenNode) `underpaid` / `refunded` | | `amount` / `currency` | The invoice amount and currency you created it with | | `openNodeChargeId` / `providerChargeId` | The provider's charge ID (`provider: "strike"` is included on the Strike path) | | `paidAt` | When Lightning Enable processed the provider event (UTC) | | `metadata` | The metadata JSON string you supplied at creation, or `null` | ### L402 producer events If you sell through the [L402 producer API](/products/agentic-commerce/producer-api-reference), a second event type arrives on the same URL: ```json { "event": "l402.challenge.paid", "paymentHash": "abc123def456...", "resource": "/api/premium/weather", "amountSats": 100, "paidAt": "2026-09-05T18:00:41Z", "idempotencyKey": "req-abc-123" } ``` | Field | Description | |-------|-------------| | `event` | Always `l402.challenge.paid`. Its presence is what distinguishes this from a payment event | | `paymentHash` | Hex payment hash of the challenge's invoice — your correlation handle, and the key to dedupe on | | `resource` | The resource path the challenge was minted for | | `amountSats` | Price in satoshis the challenge required | | `paidAt` | When payment was first proven (UTC) | | `idempotencyKey` | The `Idempotency-Key` the challenge was minted under, or `null` | **Fires once per challenge**, on the first successful verification of a credential minted from it — through `/api/l402/challenges/verify`, `/verify-credential`, or an L402-gated proxy request. L402 tokens stay valid for repeated use until their `expires` caveat, so the same credential verifies many times per paid invoice; only the first transition notifies. **"Paid" means proven, not settled.** The payer can only hold a valid credential by having settled the invoice with your payment provider — but the sats moved when the invoice was paid, which may be moments before this event. Lightning Enable does not hold funds; your provider is the record of what you were paid. An invoice that was paid but whose credential is never presented back produces no event; reconcile those with `GET /api/l402/challenges?status=unpaid`. No callback URL configured means no event is sent and nothing is queued. ## Configuring Webhooks Set your webhook URL + secret from the Lightning Enable dashboard. Navigate to **Dashboard → Settings → Webhooks** and provide: - Webhook URL (must be HTTPS in production) - Signing secret (used for HMAC verification) ### Webhook URL Requirements - Must be HTTPS in production - Must return a 2xx status within **10 seconds** (the delivery request times out after 10s) - Must be publicly accessible ## Verifying Webhooks Every webhook request from Lightning Enable includes an `X-LightningEnable-Signature` header. You **must** verify this signature to confirm that the webhook is authentic and has not been tampered with. ### Signature Format The signature header uses a timestamped HMAC scheme: ```http POST /webhooks/lightning HTTP/1.1 Content-Type: application/json X-LightningEnable-Signature: t=1704067200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8f9 ``` The header contains two comma-separated components: | Component | Description | |-----------|-------------| | `t` | Unix timestamp (seconds) when the signature was generated | | `v1` | HMAC-SHA256 hex digest of the signed payload | ### Verification Steps To verify a webhook signature: 1. **Extract** the timestamp (`t`) and signature (`v1`) from the `X-LightningEnable-Signature` header 2. **Construct** the signed payload by concatenating the timestamp, a period (`.`), and the raw request body: `{timestamp}.{payload}` 3. **Compute** the HMAC-SHA256 of the signed payload using your webhook secret as the key 4. **Compare** the computed signature with the `v1` value using a constant-time comparison function to prevent timing attacks 5. **Check freshness** -- reject signatures where the timestamp is more than 5 minutes old to prevent replay attacks ### Replay Protection Lightning Enable enforces a **5-minute tolerance** on webhook signatures. You should implement the same check on your end: - Reject webhooks where `current_time - t > 300 seconds` (5 minutes) - Optionally reject webhooks where `t` is more than 30 seconds in the future (clock skew protection) This prevents attackers from intercepting a valid webhook and replaying it later. ### Node.js / JavaScript Verification ```javascript const crypto = require('crypto'); const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; const TOLERANCE_SECONDS = 300; // 5 minutes /** * Parse the X-LightningEnable-Signature header into its components. */ function parseSignatureHeader(header) { const parts = header.split(','); let timestamp = null; let signature = null; for (const part of parts) { const trimmed = part.trim(); if (trimmed.startsWith('t=')) { timestamp = parseInt(trimmed.slice(2), 10); } else if (trimmed.startsWith('v1=')) { signature = trimmed.slice(3); } } return { timestamp, signature }; } /** * Verify a webhook signature with replay protection. * Returns true if the signature is valid and the timestamp is fresh. */ function verifyWebhookSignature(payload, signatureHeader, secret) { const { timestamp, signature } = parseSignatureHeader(signatureHeader); if (!timestamp || !signature) { return false; } // Check timestamp freshness (replay protection) const now = Math.floor(Date.now() / 1000); if (Math.abs(now - timestamp) > TOLERANCE_SECONDS) { return false; } // Compute expected signature over "{timestamp}.{payload}" const signedPayload = `${timestamp}.${payload}`; const expected = crypto .createHmac('sha256', secret) .update(signedPayload) .digest('hex'); // Constant-time comparison return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } // Express.js example -- use express.raw() to get the raw body app.post('/webhooks/lightning', express.raw({ type: 'application/json' }), (req, res) => { const signatureHeader = req.headers['x-lightningenable-signature']; const payload = req.body.toString('utf8'); if (!signatureHeader || !verifyWebhookSignature(payload, signatureHeader, WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(payload); handleWebhookEvent(event); res.status(200).send('OK'); }); ``` :::caution Use the raw request body You must verify the signature against the **raw request body** string, not a re-serialized JSON object. Re-serialization may change whitespace or key ordering, causing signature mismatches. ::: ### C# Verification ```csharp using System.Security.Cryptography; using System.Text; using System.Text.Json; [ApiController] [Route("webhooks")] public class WebhookController : ControllerBase { private readonly string _webhookSecret; private const int ToleranceSeconds = 300; // 5 minutes public WebhookController(IConfiguration config) { _webhookSecret = config["WebhookSecret"]!; } [HttpPost("lightning")] public async Task HandleWebhook() { // Read the raw body using var reader = new StreamReader(Request.Body); var payload = await reader.ReadToEndAsync(); var signatureHeader = Request.Headers["X-LightningEnable-Signature"].FirstOrDefault(); if (string.IsNullOrEmpty(signatureHeader) || !VerifySignature(payload, signatureHeader)) { return Unauthorized("Invalid signature"); } var webhookEvent = JsonSerializer.Deserialize(payload); await ProcessEventAsync(webhookEvent); return Ok(); } private bool VerifySignature(string payload, string signatureHeader) { // Parse "t={timestamp},v1={signature}" if (!TryParseSignatureHeader(signatureHeader, out var timestamp, out var providedSignature)) { return false; } // Replay protection: reject if older than 5 minutes var signatureTime = DateTimeOffset.FromUnixTimeSeconds(timestamp); var age = DateTimeOffset.UtcNow - signatureTime; if (age.TotalSeconds > ToleranceSeconds || age.TotalSeconds < -30) { return false; } // Compute HMAC-SHA256 over "{timestamp}.{payload}" var signedPayload = $"{timestamp}.{payload}"; using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_webhookSecret)); var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(signedPayload)); var expectedSignature = Convert.ToHexString(hash).ToLowerInvariant(); // Constant-time comparison return CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(expectedSignature), Encoding.UTF8.GetBytes(providedSignature)); } private static bool TryParseSignatureHeader(string header, out long timestamp, out string signature) { timestamp = 0; signature = string.Empty; foreach (var part in header.Split(',')) { var trimmed = part.Trim(); if (trimmed.StartsWith("t=") && long.TryParse(trimmed[2..], out timestamp)) { } else if (trimmed.StartsWith("v1=")) { signature = trimmed[3..].ToLowerInvariant(); } } return timestamp > 0 && !string.IsNullOrEmpty(signature); } } ``` ### Python Verification ```python import hmac import hashlib import time WEBHOOK_SECRET = os.environ['WEBHOOK_SECRET'] TOLERANCE_SECONDS = 300 # 5 minutes def parse_signature_header(header): """Parse the X-LightningEnable-Signature header.""" timestamp = None signature = None for part in header.split(','): trimmed = part.strip() if trimmed.startswith('t='): timestamp = int(trimmed[2:]) elif trimmed.startswith('v1='): signature = trimmed[3:] return timestamp, signature def verify_webhook_signature(payload, signature_header, secret): """Verify a webhook signature with replay protection.""" timestamp, signature = parse_signature_header(signature_header) if timestamp is None or signature is None: return False # Replay protection: reject if older than 5 minutes now = int(time.time()) if abs(now - timestamp) > TOLERANCE_SECONDS: return False # Compute HMAC-SHA256 over "{timestamp}.{payload}" signed_payload = f'{timestamp}.{payload}' expected = hmac.new( secret.encode('utf-8'), signed_payload.encode('utf-8'), hashlib.sha256 ).hexdigest() # Constant-time comparison return hmac.compare_digest(signature, expected) # Flask example @app.route('/webhooks/lightning', methods=['POST']) def handle_webhook(): signature_header = request.headers.get('X-LightningEnable-Signature') payload = request.get_data(as_text=True) if not signature_header or not verify_webhook_signature(payload, signature_header, WEBHOOK_SECRET): return 'Invalid signature', 401 event = request.json process_event(event) return 'OK', 200 ``` ## Handling Events ### Best Practices 1. **Return 200 quickly** - Process asynchronously if needed 2. **Handle duplicates** - Events may be sent multiple times 3. **Verify signatures** - Always validate HMAC 4. **Log everything** - Keep records for debugging ### Idempotency Use the invoice/refund ID to handle duplicate events: ```javascript async function handlePaid(payload) { const { invoiceId } = payload; // Check if already processed const existing = await db.orders.findOne({ lightningInvoiceId: invoiceId, status: 'fulfilled' }); if (existing) { console.log('Already processed:', invoiceId); return; } // Process the payment await db.orders.updateOne( { lightningInvoiceId: invoiceId }, { $set: { status: 'fulfilled', paidAt: payload.paidAt } } ); await fulfillOrder(invoiceId); } ``` ### Routing on `event`, then `status` Check for an `event` field first — that is an L402 producer event. Everything else is a payment event, dispatched on `status`: ```javascript app.post('/webhooks/lightning', async (req, res) => { const payload = req.body; // verify the signature first — see above if (payload.event === 'l402.challenge.paid') { await handleChallengePaid(payload); // dedupe on payload.paymentHash return res.status(200).send('OK'); } switch (payload.status) { case 'paid': await handlePaid(payload); break; case 'expired': await handleExpired(payload); break; default: // processing, underpaid, etc. — log and wait for a terminal status console.log('Payment update:', payload.invoiceId, payload.status); } res.status(200).send('OK'); }); ``` ## Delivery & Reliability Webhook delivery is a **fast-path notification, not a guaranteed delivery channel**: - Each delivery attempt has a **10-second timeout**; the first attempt fires as soon as Lightning Enable processes the provider's webhook. - If your endpoint is down or errors, delivery is retried with exponential backoff — 30s, 60s, 120s, 240s, 480s (5 retry attempts, ~16-minute total window) — with **identical payload bytes** on every attempt (each attempt's `X-LightningEnable-Signature` is freshly timestamped and verifies against that same body — dedupe on payload content like `invoiceId` + `status`, never on the signature header) *(as of the July 2026 update; earlier versions did not retry failed forwards)*. - After retries exhaust, the event is marked permanently failed — recover by polling. **Recovery pattern:** on startup or on a schedule, reconcile any orders still pending on your side via `GET /api/payments/{invoiceId}` (authoritative status), or force a provider re-check with `POST /api/payments/{invoiceId}/sync`. ## Testing Webhooks ### Local Development Use ngrok to expose your local server: ```bash # Start your local server npm start # Runs on http://localhost:3000 # In another terminal ngrok http 3000 # Use the ngrok URL as your webhook endpoint # https://abc123.ngrok.io/webhooks/lightning ``` ### Test Event Send a test webhook to verify your endpoint: ```bash # Generate a test signature TIMESTAMP=$(date +%s) PAYLOAD='{"invoiceId":"1042","orderId":"ORDER-TEST","status":"paid","amount":25.00,"currency":"USD","openNodeChargeId":"test-charge","paidAt":"2026-07-03T12:05:00Z","metadata":null}' SIG=$(echo -n "${TIMESTAMP}.${PAYLOAD}" | openssl dgst -sha256 -hmac "your-webhook-secret" | cut -d' ' -f2) curl -X POST https://your-site.com/webhooks/lightning \ -H "Content-Type: application/json" \ -H "X-LightningEnable-Signature: t=${TIMESTAMP},v1=${SIG}" \ -d "${PAYLOAD}" ``` ## Troubleshooting ### Webhook Not Received 1. **Check URL** - Ensure webhook URL is correct and HTTPS 2. **Check firewall** - Allow incoming connections 3. **Test manually** - Use curl to test your endpoint ### Signature Mismatch 1. **Check secret** - Ensure webhook secret matches what you configured 2. **Check encoding** - Use the raw request body, not a re-serialized JSON object 3. **Check algorithm** - Use HMAC-SHA256 over `{timestamp}.{payload}` 4. **Check header name** - The header is `X-LightningEnable-Signature`, not `X-Webhook-Signature` 5. **Check timestamp** - Ensure replay protection tolerance is at least 5 minutes ### Timeout Errors 1. **Process async** - Return 200 immediately, process in background 2. **Respond fast** - The delivery request times out after 10 seconds 3. **Recover by polling** - Missed a webhook? `GET /api/payments/{invoiceId}` is authoritative ## Security Best Practices ### Always Verify Signatures ```javascript // Bad - No verification app.post('/webhooks', express.json(), (req, res) => { processEvent(req.body); res.send('OK'); }); // Good - Verify signature with raw body app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => { const sigHeader = req.headers['x-lightningenable-signature']; const payload = req.body.toString('utf8'); if (!sigHeader || !verifyWebhookSignature(payload, sigHeader, WEBHOOK_SECRET)) { return res.status(401).send('Unauthorized'); } const event = JSON.parse(payload); processEvent(event); res.send('OK'); }); ``` ### Use HTTPS Always use HTTPS for webhook endpoints to prevent interception. ### Validate Event Data Don't trust webhook data blindly: ```javascript async function handlePayment(data) { // Verify with API const payment = await getPayment(data.invoiceId); if (payment.status !== 'paid') { throw new Error('Payment not actually paid'); } // Now safe to fulfill await fulfillOrder(payment.orderId); } ``` ## Next Steps - [Payments API](/api-reference/payments) - Create payments - [Errors](/api-reference/errors) - Error handling - [Rate Limiting](/api-reference/rate-limiting) - API limits ============================================================================== # Core Concepts Source: https://docs.lightningenable.com/concepts ============================================================================== # Core Concepts This section covers the fundamental concepts that underpin Lightning Enable as a settlement layer. ## Settlement vs Payment A **payment** implies a human transaction: invoices, receipts, reconciliation, dispute resolution. A **settlement** is a primitive: value transferred, proof returned, state updated. No interpretation required. Lightning Enable operates at the settlement layer. The business logic that interprets settlements as payments, subscriptions, or access grants lives in your application. ## The Request-Settlement Model Traditional payment flows: ``` Request → Queue → Batch → Process → Confirm → Settle (hours to days) ``` Settlement flows: ``` Request → Settle → Response (milliseconds) ``` Settlement happens synchronously with the request. When your code returns, settlement is final. ## Cryptographic Proof Every settlement produces a **preimage** - a 32-byte value that proves settlement occurred. The SHA-256 hash of the preimage matches the payment hash embedded in the Lightning invoice. ``` invoice contains: payment_hash settlement returns: preimage verification: SHA256(preimage) == payment_hash ``` This proof is cryptographically unforgeable. Either settlement happened and you have the preimage, or it did not. ## API Middleware Architecture Lightning Enable never holds funds. Your payment provider (Strike or OpenNode) facilitates custody and settlement, executing the actual value transfer on Lightning Network. ``` Your App → Lightning Enable → Payment Provider → Lightning Network (API middleware) (custody) (settlement) ``` We are infrastructure. Your payment provider is the custodian. Lightning Network is the settlement rail. ## Invoice Lifecycle 1. **`unpaid`** - Invoice generated, awaiting settlement 2. **`processing`** - Settlement detected, confirming 3. **`paid`** - Settlement complete, preimage available 4. **`expired`** - Invoice timeout, no settlement These are the literal `status` values in the [Payments API](/api-reference/payments#payment-statuses). Invoices are ephemeral. Create them on demand, let them expire if unused. ## Further Reading - [Settlement Flows](/settlement-flows) - Technical flow documentation - [Economic Patterns](/economic-patterns) - Why per-request economics matter - [L402 Protocol](/products/agentic-commerce/overview) - HTTP-native settlement ============================================================================== # AI Spending Guidelines Source: https://docs.lightningenable.com/configuration/ai-spending-guidelines ============================================================================== # AI Spending Guidelines This guide provides recommended budget configurations for different AI agent use cases when using the `pay_invoice` tool. ## How Budgets Are Configured There are two layers of spending control, and they work together: 1. **Operator limits (USD)** — set in `~/.lightning-enable/config.json` under `limits.maxPerPayment` and `limits.maxPerSession`. Only you can edit this file; no MCP tool can raise these values. 2. **Runtime sats caps (tighten-only)** — the `configure_budget` MCP tool lets an agent *lower* its per-request / per-session sats caps mid-session. It can **never raise** them above your config-file limits. Parameter names: `per_request` / `per_session` in the Python package, `perRequest` / `perSession` in .NET. 3. **Out-of-band confirmation** — payments above the auto-approve threshold return `requiresConfirmation=true` and the server prints a confirmation code to its console; you relay the code to the agent, which re-calls the original payment tool with `confirmation_nonce=`. Full flow: [AI Spending Security](/products/agentic-commerce/ai-spending-security). :::note Legacy env vars removed The `L402_MAX_SATS_PER_REQUEST` and `L402_MAX_SATS_PER_SESSION` environment variables have been **removed from both packages** (.NET and Python). Setting them does nothing. Use `~/.lightning-enable/config.json` for operator limits and `configure_budget` for runtime tightening. ::: ## Quick Reference | Use Case | Per Request | Per Session | Wallet Balance | |----------|-------------|-------------|----------------| | Testing/Development | 100 sats | 1,000 sats | 5,000 sats | | Light Browsing | 50 sats | 500 sats | 2,000 sats | | Research Sessions | 200 sats | 2,000 sats | 10,000 sats | | API Integration | 500 sats | 5,000 sats | 20,000 sats | | Heavy Usage | 1,000 sats | 10,000 sats | 50,000 sats | To apply a row: set USD equivalents in `config.json` (the hard ceiling), then have the agent call `configure_budget` with the sats values (the runtime cap). ## Configuration Examples ### Minimal (Testing) For initial testing and learning, set tight USD limits in `~/.lightning-enable/config.json`: ```json { "limits": { "maxPerPayment": 0.10, "maxPerSession": 1.00 } } ``` Then tighten the runtime sats caps at the start of the session: ``` configure_budget per_request=100 per_session=1000 ``` **Wallet funding:** 5,000 sats (~$5) **Use for:** - Learning how pay_invoice works - Testing your MCP configuration - Paying a few test invoices ### Conservative (Light Usage) For occasional, supervised use: ```json { "limits": { "maxPerPayment": 0.05, "maxPerSession": 0.50 } } ``` ``` configure_budget per_request=50 per_session=500 ``` **Wallet funding:** 2,000 sats (~$2) **Use for:** - Accessing a few L402-protected resources - Light browsing of paid content - One-off API calls ### Moderate (Research) For research sessions with multiple data sources: ```json { "limits": { "maxPerPayment": 0.25, "maxPerSession": 2.00 } } ``` ``` configure_budget per_request=200 per_session=2000 ``` **Wallet funding:** 10,000 sats (~$10) **Use for:** - Research involving multiple paid APIs - Gathering data from various sources - Extended work sessions ### Professional (API Integration) For development and integration work: ```json { "limits": { "maxPerPayment": 0.50, "maxPerSession": 5.00 } } ``` ``` configure_budget per_request=500 per_session=5000 ``` **Wallet funding:** 20,000 sats (~$20) **Use for:** - Testing API integrations - Development workflows - Automated data collection (supervised) ## Cost Estimation ### Common Operations | Operation | Typical Cost | |-----------|--------------| | Single API call | 1-100 sats | | Premium content access | 10-500 sats | | AI model API (per request) | 50-200 sats | | Data feed query | 10-100 sats | | Image generation | 100-1000 sats | | **Lightning Enable Store purchase** | **25,000-45,000+ sats** | :::warning Store Purchases Require Higher Limits [Lightning Enable Store](https://store.lightningenable.com) products cost **25,000-45,000+ sats** (including shipping). Default budget limits will reject these payments. To purchase from the store, raise the limits in `~/.lightning-enable/config.json` (only you can do this — no MCP tool can raise limits): ```json { "limits": { "maxPerPayment": 50.00, "maxPerSession": 100.00 } } ``` Where the payment tool takes a `maxSats` / `max_sats` parameter (`access_l402_resource`, `pay_l402_challenge`, and the Python `pay_invoice`), also pass a large enough value (e.g., `maxSats=50000`) — the per-call default is 1,000 sats. ::: ### Session Estimates | Task Type | Estimated Cost | |-----------|----------------| | 10-minute research | 100-500 sats | | 1-hour development session | 500-2,000 sats | | Day of API testing | 2,000-5,000 sats | ## Session Management ### Starting a Session 1. **Check wallet balance** before starting 2. **Review budget limits** - are they appropriate? 3. **Define scope** - what will the AI be doing? 4. **Monitor actively** during first few uses ``` # Check balance before starting get_balance # Verify budget configuration get_budget_status # Shows current limits and session spending ``` ### During a Session - Watch for unexpected payment patterns - If limits are hit, only the **operator** can grant more budget — by editing `~/.lightning-enable/config.json` (both packages; applied at restart). An agent cannot raise its own limits - To narrow scope mid-session, tighten the caps further: ``` # configure_budget can only LOWER limits, never raise them # (param names: per_request / per_session in the Python package; perRequest / perSession in .NET) configure_budget per_request=200 per_session=2000 ``` ### Ending a Session - Review all payments made - Check remaining balance - Note any unexpected charges ``` # Review what was spent get_payment_history limit=50 ``` ## Budget Strategy ### Start Low Begin with minimal limits and increase only as needed: 1. Week 1: 100 sats/request, 1,000 sats/session 2. Week 2: Evaluate - were limits hit? Why? 3. Week 3: Adjust based on actual usage patterns 4. Ongoing: Find your comfortable baseline ### Per-Request vs Per-Session **Per-request limit** protects against: - Single large accidental payment - Overpaying for one service **Per-session limit** protects against: - Many small payments adding up - Extended sessions draining funds - Runaway loops ### When to Increase Limits Increase limits when: - You consistently hit limits for legitimate use - You understand why limits are being reached - You're comfortable with the higher exposure Do NOT increase limits: - Just because limits were hit - Without understanding what payments were made - To avoid supervision ## Refill Strategy ### Recommended Approach 1. **Keep wallet balance low** - Only what you need for near-term use 2. **Refill frequently** in small amounts 3. **Never exceed** what you're willing to lose 4. **Track refills** to understand true spending over time ### Example Refill Schedule | Usage Level | Refill Amount | Frequency | |-------------|---------------|-----------| | Light | 5,000 sats | Monthly | | Moderate | 10,000 sats | Bi-weekly | | Heavy | 25,000 sats | Weekly | ## Troubleshooting Budget Issues ### "Exceeds per-request limit" Your payment request is larger than the effective per-request cap — either `limits.maxPerPayment` in `~/.lightning-enable/config.json`, a tighter runtime cap the agent set via `configure_budget`, or the tool call's own `maxSats` parameter. **Options:** 1. Increase `limits.maxPerPayment` in the config file (operator only) if the payment is legitimate, and pass a larger `maxSats` on the tool call 2. Find alternative service with lower cost 3. Skip the payment if not essential ### "Would exceed session budget" You've spent most of your session budget. **Options:** 1. Start a fresh session (restart the MCP server) — the session counter is not agent-resettable 2. Increase the session budget in `~/.lightning-enable/config.json` (operator only, with caution) 3. End session and review payments ### Wallet balance low Your wallet/payment provider account needs more funds. **Options:** 1. Add funds via your payment provider dashboard (Strike or OpenNode) 2. Transfer from another Lightning wallet 3. Adjust budget limits to match available funds ## Related Documentation - [AI Spending Security](/products/agentic-commerce/ai-spending-security) - Security best practices - [Legal Considerations](/configuration/legal-considerations) - Liability and terms - [MCP Configuration](/products/agentic-commerce/ai-agent-integration) - Setting up MCP ============================================================================== # API Key Management Source: https://docs.lightningenable.com/configuration/api-key-management ============================================================================== # API Key Management Your API key is the credential that authenticates your requests to the Lightning Enable API. This guide covers everything you need to know about API keys: how they work, where to find them, how to manage them, and security best practices. ## Understanding API Keys ### What Is Your API Key? Your Lightning Enable API key is a unique, randomly-generated credential that: - **Identifies your merchant account** to the Lightning Enable API - **Authenticates all your API requests** via the `X-API-Key` header - **Provides access** to create invoices, check payment status, and manage your integration ``` Example API key format: YOUR_API_KEY_HERE ``` ### API Key vs Provider API Key You have **two different keys** - don't confuse them: | Key | Purpose | Source | Used By | |-----|---------|--------|---------| | **Lightning Enable API Key** | Authenticate to Lightning Enable | Generated at signup | Your application → Lightning Enable | | **Provider API Key (Strike or OpenNode)** | Connect Lightning Enable to your payment provider | From your provider dashboard | Lightning Enable → Strike or OpenNode | ``` Your App Lightning Enable Strike or OpenNode │ │ │ │ X-API-Key: abc123... │ │ ├─────────────────────────────►│ │ │ (Lightning Enable API Key) │ │ │ │ Authorization: xyz789... │ │ ├─────────────────────────►│ │ │ (Provider API Key) │ ``` ## Getting Your API Key ### At Signup When you complete your subscription checkout: 1. **Stripe processes your payment** 2. **Lightning Enable generates your API key** 3. **Success page displays your key** with copy button :::warning Save Your Key Immediately The success page is the only place your key is shown at signup — it is **not** emailed to you. Save it to a secure location (password manager, environment variables, secret manager) before navigating away. If you lose it, view or regenerate it in the dashboard. ::: ### Key Management Dashboard Access your key anytime at: **https://api.lightningenable.com/dashboard/settings** The dashboard allows you to: - View your current API key (masked by default) - Reveal your full key with one click - Regenerate your key if needed - See when your key was last changed ## Using Your API Key ### In API Requests Include your API key in the `X-API-Key` header: ```bash curl -X POST https://api.lightningenable.com/api/payments \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR_API_KEY_HERE" \ -d '{ "amount": 10.00, "currency": "USD", "description": "Product purchase" }' ``` ### In Application Code #### .NET / C# ```csharp var client = new HttpClient(); client.DefaultRequestHeaders.Add("X-API-Key", Environment.GetEnvironmentVariable("LIGHTNING_ENABLE_API_KEY")); var response = await client.PostAsync("https://api.lightningenable.com/api/payments", content); ``` #### Node.js / JavaScript ```javascript const response = await fetch('https://api.lightningenable.com/api/payments', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.LIGHTNING_ENABLE_API_KEY }, body: JSON.stringify({ amount: 10.00, currency: 'USD' }) }); ``` #### Python ```python import os import requests response = requests.post( 'https://api.lightningenable.com/api/payments', headers={ 'Content-Type': 'application/json', 'X-API-Key': os.environ['LIGHTNING_ENABLE_API_KEY'] }, json={'amount': 10.00, 'currency': 'USD'} ) ``` ## Managing Your API Key ### Viewing Your Key 1. Go to **https://api.lightningenable.com/dashboard/settings** 2. Sign in to the dashboard (with your current API key or a magic link) 3. In the **API Key** card, reveal the key 4. Copy it to your clipboard ### Regenerating Your Key If your key is compromised or you want to rotate it for security: 1. Go to **https://api.lightningenable.com/dashboard/settings** 2. In the **API Key** card, click **"Regenerate API key"** 3. Confirm the action in the dialog 4. **Copy your new key immediately** 5. Update all your applications with the new key :::danger Key Regeneration is Immediate and Irreversible - Your **old key is invalidated instantly** - Any application using the old key will receive `401 Unauthorized` - You **cannot recover the old key** - Plan your key rotation carefully to minimize downtime ::: ### Key Rotation Best Practices For production systems, follow this rotation procedure: 1. **Prepare** - Have your deployment process ready 2. **Regenerate** - Get the new key from the dashboard 3. **Update secrets** - Deploy new key to all environments 4. **Verify** - Test API calls with new key 5. **Monitor** - Watch for any failed authentications ```bash # Example: Updating Azure App Service az webapp config appsettings set \ --name your-app \ --resource-group your-rg \ --settings "LIGHTNING_ENABLE_API_KEY=your-new-key" ``` ## Security Best Practices ### Do's | Practice | Why | |----------|-----| | Store in environment variables | Keeps keys out of code | | Use a secret manager | Centralized, audited secret storage | | Rotate keys periodically | Limits exposure if compromised | | Use different keys per environment | Isolates dev/staging/prod | | Monitor for unauthorized use | Detect compromises early | ### Don'ts | Anti-Pattern | Risk | |--------------|------| | Hardcode in source code | Keys in git history forever | | Commit to version control | Public exposure | | Share via email/chat | Keys in searchable logs | | Log API keys | Exposure in log aggregators | | Use same key everywhere | Blast radius if compromised | ### Environment Variable Examples **Linux/macOS:** ```bash export LIGHTNING_ENABLE_API_KEY="YOUR_API_KEY_HERE" ``` **Windows PowerShell:** ```powershell $env:LIGHTNING_ENABLE_API_KEY = "YOUR_API_KEY_HERE" ``` **Docker:** ```dockerfile ENV LIGHTNING_ENABLE_API_KEY=${LIGHTNING_ENABLE_API_KEY} ``` **.env file (local development only):** ``` LIGHTNING_ENABLE_API_KEY=YOUR_API_KEY_HERE ``` :::warning Never Commit .env Files Add `.env` to your `.gitignore` file. ::: ### Secret Managers For production, use a proper secret manager: | Platform | Service | |----------|---------| | Azure | Key Vault | | AWS | Secrets Manager | | Google Cloud | Secret Manager | | Kubernetes | Secrets | | HashiCorp | Vault | **Azure Key Vault Example:** ```bash # Store secret az keyvault secret set \ --vault-name your-vault \ --name "LightningEnableApiKey" \ --value "YOUR_API_KEY_HERE" # Retrieve in app var secret = await secretClient.GetSecretAsync("LightningEnableApiKey"); var apiKey = secret.Value.Value; ``` ## Troubleshooting ### "API key required" ```json {"error": "API key required", "message": "Please provide API key in X-API-Key header"} ``` **Cause:** Missing `X-API-Key` header **Fix:** Add the header to your request: ```bash -H "X-API-Key: YOUR_KEY_HERE" ``` ### "Invalid API key" ```json {"error": "Invalid API key", "message": "The provided API key is invalid or inactive"} ``` **Causes:** - Key was regenerated (old key no longer valid) - Typo in the key - Key from wrong environment (dev vs prod) - Subscription expired or canceled **Fix:** 1. Check you're using the correct, current key 2. Verify your subscription status 3. Regenerate a new key if needed ### "Subscription inactive" ```json {"error": "Subscription inactive", "message": "Your subscription is not active"} ``` **Cause:** Stripe subscription is past_due, canceled, or expired **Fix:** 1. Check your Stripe subscription status 2. Update payment method if needed 3. Contact support if issue persists ## API Key Lifecycle ``` ┌─────────────────────────────────────────────────────────────────┐ │ API Key Lifecycle │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ SIGNUP │───►│ ACTIVE │───►│ REGENERATE│───►│ ACTIVE │ │ │ │ │ │ │ │ │ │ (new key) │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ Key shown on Key used for Old key New key │ │ success page all API calls invalidated now active │ │ + welcome email immediately │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ## Related Documentation - [Getting Started](/getting-started/quick-start) - Initial setup guide - [API Reference](/api-reference/authentication) - Authentication details - [Payment Provider Setup](/opennode-setup/api-keys) - Configuring your provider API key - [Webhooks](/api-reference/webhooks) - Securing webhook endpoints ## FAQ ### Can I have multiple API keys? Currently, each merchant account has one API key. If you need separate keys for different environments, consider separate subscriptions for dev/staging/prod. ### How long is an API key valid? API keys don't expire based on time. They remain valid until: - You regenerate the key - Your subscription is canceled - Your account is deactivated ### Can I see my old API key? No. When you regenerate, the old key is permanently invalidated and cannot be retrieved. This is a security feature. ### What if I lose my API key? 1. Log into the Key Management Dashboard 2. Regenerate a new key 3. Update all your applications ### Is the API key transmitted securely? Yes. All API requests must use HTTPS, encrypting the key in transit. The key is stored encrypted at rest in our database. ============================================================================== # Environment Variables Source: https://docs.lightningenable.com/configuration/environment-variables ============================================================================== # Environment Variables Lightning Enable is a **hosted SaaS**. The API at `api.lightningenable.com` is deployed and operated by Lightning Enable — as a merchant you never run the server or set its server-side configuration. Your setup happens in the [dashboard](https://api.lightningenable.com/dashboard) and via the [merchant API](/api-reference/merchant-settings). This page covers two audiences: - **[For merchants](#for-merchants-mcp-server-configuration)** — environment variables for the open-source **MCP server** that you run on your own machine so AI agents can pay over Lightning. - **[Platform operator reference](#platform-operator-reference-lightning-enable-staff)** — server-side settings used by Lightning Enable staff to operate the hosted platform. Documented for transparency; merchants never set these. ## For merchants: MCP server configuration The MCP (Model Context Protocol) server enables AI agents to use Lightning Enable tools. It runs locally (or wherever your agent runs) and is configured entirely through environment variables and a local config file — none of this touches the hosted platform. ### Wallet Configuration The MCP server is open-source (MIT) and free to install. Wallet, invoice, L402, budget, and API-discovery tools work out of the box with just a wallet configured below. Producer tools (sell access via L402) and Agent Service Agreement tools (agent-to-agent commerce over Nostr) unlock with a Lightning Enable API key. See the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the full tool list. Choose one wallet provider (listed in default priority order): **LND (Best for L402):** ```bash LND_REST_HOST=https://your-lnd-node:8080 LND_MACAROON_HEX=your-admin-macaroon-hex ``` **Nostr Wallet Connect (NWC):** ```bash NWC_CONNECTION_STRING=nostr+walletconnect://pubkey?relay=wss://relay.example.com&secret=xxx ``` **Strike (Recommended for USD users):** ```bash STRIKE_API_KEY=your-strike-api-key ``` **OpenNode:** ```bash OPENNODE_API_KEY=your-opennode-api-key OPENNODE_ENVIRONMENT=production # or "dev" for testnet ``` :::info Wallet Priority If multiple wallet credentials are configured, they are used in this order: 1. LND (if `LND_REST_HOST` and `LND_MACAROON_HEX` are set) 2. NWC (if `NWC_CONNECTION_STRING` is set) 3. Strike (if `STRIKE_API_KEY` is set) 4. OpenNode (if `OPENNODE_API_KEY` is set) This order prioritizes wallets that return a preimage, which is required for L402. OpenNode does **not** return preimages, so L402 will not work with it. You can override the priority with the `WALLET_PRIORITY` environment variable (values: `lnd`, `nwc`, `strike`, `opennode`). Only the first configured wallet is used. ::: ### Spending Limits Configuration Budget limits are configured via `~/.lightning-enable/config.json`: ```json { "currency": "USD", "tiers": { "autoApprove": 0.10, "logAndApprove": 1.00, "formConfirm": 10.00, "urlConfirm": 100.00 }, "limits": { "maxPerPayment": 500.00, "maxPerSession": 100.00 } } ``` This file is created automatically on first run (or written by the `setup_wallet` tool). AI agents **cannot modify** this file through any MCP tool (an agent with direct shell or filesystem access to the host is outside this guarantee). At runtime, an agent can *tighten* its own caps via the `budget` tool's `action="tighten"` (formerly the standalone `configure_budget` tool), but it can never raise them above the limits in this file. See [AI Spending Security](/products/agentic-commerce/ai-spending-security) for detailed configuration. ### MCP Configuration Summary | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `LND_REST_HOST` | If using LND | - | LND REST API host | | `LND_MACAROON_HEX` | If using LND | - | LND admin macaroon in hex | | `NWC_CONNECTION_STRING` | If using NWC | - | Nostr Wallet Connect URI | | `STRIKE_API_KEY` | If using Strike | - | Strike API key (preferred for USD) | | `OPENNODE_API_KEY` | If using OpenNode | - | OpenNode API key with withdrawal permissions | | `OPENNODE_ENVIRONMENT` | No | `production` | `production` or `dev` | | `WALLET_PRIORITY` | No | - | Override default wallet priority (`lnd`, `nwc`, `strike`, or `opennode`) | | `LIGHTNING_ENABLE_API_KEY` | No | - | Merchant API key; required by every `l402_producer` action and the `agent_services` actions `request`/`publish`/`unpublish`/`attest` (`discover`/`settle`/`reputation` work without it) | | `LIGHTNING_ENABLE_TOOL_PROFILE` | No | `standard` | `lite` (pay and check the wallet only), `standard` (the full current surface), or `full` (standard plus every pre-consolidation tool name as a deprecated alias, removed in v3.0.0) — see the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide#tool-profiles) | | `LIGHTNING_ENABLE_HOSTED` | No | unset | Set to `1` in a non-interactive/hosted deployment so out-of-band confirmation defaults to refusing (rather than printing to stderr) when the process isn't attached to a TTY — see [AI Spending Security](/products/agentic-commerce/ai-spending-security#confirmation-channels) | :::tip Open-Source MCP Server The MCP server is open-source (MIT) and free to install. Wallet, invoice, L402, budget, and API-discovery tools work out of the box with just a wallet — no account or API key required. Producer tools (sell access via L402) and the ASA request/publish/unpublish tools unlock with a Lightning Enable API key; ASA discovery, settlement, and reputation reads work with just a wallet. See the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the full tool list. ::: :::note Merchant payment provider keys are NOT environment variables Your Strike or OpenNode API key for **accepting payments** through Lightning Enable is configured in the [dashboard](https://api.lightningenable.com/dashboard) (Settings → Payment Provider) or via the merchant API (`PUT /api/merchant/strike-key` / `PUT /api/merchant/opennode-key`) — never through environment variables. The `STRIKE_API_KEY` / `OPENNODE_API_KEY` variables above configure the **MCP server's outgoing wallet** only. ::: ## Platform operator reference (Lightning Enable staff) :::warning Merchants never set these Lightning Enable is hosted SaaS — everything below configures the platform itself and is managed by Lightning Enable staff on the production deployment. It is documented here for transparency and for internal operations. If you are a merchant, nothing in this section applies to you. ::: ### Required Configuration #### Database Encryption Key ```bash DB_ENCRYPTION_KEY=your-base64-encoded-32-byte-key ``` **Purpose:** Encrypts sensitive fields (API keys, provider keys) at rest using AES-256-GCM. **Generate a secure key:** ```bash # Linux/Mac openssl rand -base64 32 # PowerShell [Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Maximum 256 })) ``` :::danger Critical - **BACKUP THIS KEY** - If lost, all encrypted merchant data is permanently unrecoverable - **NEVER CHANGE** after deployment - existing encrypted data becomes unreadable ::: #### Admin API Key ```bash ADMIN_API_KEY=your-secure-admin-key ``` **Purpose:** Authenticates internal admin API requests and Hangfire dashboard access for platform operators. Not used by merchants. **Recommended:** Generate using the same method as DB_ENCRYPTION_KEY. #### Database Connection ```bash ConnectionStrings__DefaultConnection="Server=your-server;Database=LightningEnable;..." ``` **Purpose:** SQL Server connection string for the application database. For Azure SQL with Entra ID authentication: ``` Server=your-server.database.windows.net;Database=LightningEnable;Authentication=Active Directory Managed Identity; ``` ### Stripe Configuration All Stripe settings are required for subscription management. #### Secret Key ```bash Stripe__SecretKey=sk_live_xxxxxxxxxxxxx ``` **Purpose:** Server-side Stripe API authentication. - **Production:** Use `sk_live_...` keys - **Development:** Use `sk_test_...` keys #### Publishable Key ```bash Stripe__PublishableKey=pk_live_xxxxxxxxxxxxx ``` **Purpose:** Client-side Stripe Checkout initialization. #### Webhook Secret ```bash Stripe__WebhookSecret=whsec_xxxxxxxxxxxxx ``` **Purpose:** Verifies webhook signatures from Stripe. **Get this from:** Stripe Dashboard → Webhooks → Select endpoint → Signing secret #### Base URL ```bash Stripe__BaseUrl=https://api.lightningenable.com ``` **Purpose:** Base URL for success/cancel redirect URLs after checkout. #### Pricing Plans ```json { "Stripe": { "PricingPlans": { "individual": { "Name": "Agentic Commerce", "Description": "Full L402 access with settlement via Strike API", "PriceId": "price_xxxxxxxxxxxxx", "PriceCents": 4900, "TrialEligible": true, "Features": [ "Unlimited L402 endpoints", "Strike as settlement provider", "Per-endpoint pricing" ] }, "l402": { "Name": "Agentic Commerce — Business", "Description": "Pay-per-request API monetization", "PriceId": "price_xxxxxxxxxxxxx", "PriceCents": 29900, "TrialEligible": true, "Features": ["Everything in Agentic Commerce", "White-glove onboarding", "Direct founder access"] } } } } ``` **Purpose:** Maps plans to Stripe pricing. Each key maps to an **object** — not a bare price-ID string — with `Name`, `Description`, `PriceId`, `PriceCents`, an optional `TrialEligible`, and a `Features` array. Since the September 2026 repricing, `individual` is the only plan a new self-serve checkout can select — the `l402` key stays configured so `GetTierFromStripePriceId` can still resolve webhook and portal events for existing Business subscribers; it is not offered to new customers (Business is contact-only, see [Subscription & Plan Enforcement](/products/subscription-management)). Only `PriceId` is load-bearing. `Name`, `Description`, `PriceCents`, and `Features` are display values, and **`TrialEligible` here is not read by anything** — trial eligibility comes from the plan table in `PlanTierService`, so setting `"TrialEligible": false` on a block does not suppress the trial. Use `Stripe:TrialPeriodDays` for that. **Live keys:** `individual` and `l402`. `l402microtransactions` is also accepted as a spelling of `l402` — `GetPriceId` matches on tier identity in both directions, so a config keyed either way resolves the same price for either spelling of the request. Free has no key here. It is not purchasable through Stripe, so it has no price ID. > **Note — these keys are checkout-selection identifiers, not the stored `Merchant.PlanTier` value.** The checkout flow looks up a `PriceId` by this key (`StripeSettings.GetPriceId`), and the Stripe webhook then derives the tier persisted to `Merchant.PlanTier` from the **PriceId** via `PlanTierService.GetTierFromStripePriceId` — *not* from this key. A key therefore does not have to match the resulting tier ID. > **Note — a retired key never prices a live checkout.** Configurations written before September 2026 may still carry a `standaloneapi` or `kenticocommerce` block. Two things keep it inert. First, a request naming a retired tier never reaches the lookup: `POST /api/stripe/create-checkout-session` accepts only `individual` — `l402` and `l402microtransactions` are also rejected with a `400` since the September 2026 repricing made Business contact-only. Second, when `GetPriceId` falls back to matching config keys by tier identity, it considers only keys that name a tier still on offer — so a retired key is skipped rather than treated as a synonym for Individual. That is deliberate: the retired Kentico block carries its own price, distinct from either live plan, and matching it would charge the wrong amount. Delete retired blocks when convenient. ### Payment Provider Configuration Lightning Enable supports multiple payment providers. These settings control the platform-wide defaults; each merchant supplies their own provider API key via the dashboard or merchant API. #### Default Provider ```bash PaymentProvider__Default=Strike ``` **Values:** `Strike` (default) or `OpenNode` This sets the default provider for merchants that don't have an explicit provider configured. Per-merchant provider selection is available via the merchant settings API. #### Strike Configuration ```bash PaymentProvider__Strike__BaseUrl=https://api.strike.me/v1 ``` | Setting | Default | Description | |---------|---------|-------------| | `PaymentProvider:Strike:BaseUrl` | `https://api.strike.me/v1` | Strike API base URL. Use `https://api.dev.strike.me/v1` for sandbox. Platform-wide — not selectable per merchant. | Merchants configure their Strike API key via the merchant settings API. The key is encrypted at rest using AES-256-GCM. :::info Strike vs OpenNode | Feature | Strike | OpenNode | |---------|--------|----------| | Invoice creation | 2-step (create + quote) | Single step | | Preimage support | Direct from API | Parsed from BOLT11 | | Native refunds | No (uses payment-quotes) | Yes | | Webhook format | Thin (entityId only) | Full payload | | Currencies | BTC, USD, EUR, GBP | BTC, USD | | Best for | L402, general payments | Refund-heavy workflows | ::: #### OpenNode Environment ```bash OpenNode__Environment=production ``` **Values:** - `dev` - Uses https://dev-api.opennode.com (testnet Bitcoin) - `production` - Uses https://api.opennode.com (mainnet Bitcoin) Platform-wide — the hosted platform runs against OpenNode production, so merchants use production OpenNode API keys. #### Webhook URL (Optional) ```bash PaymentProvider__WebhookUrl=https://api.lightningenable.com/api/webhooks/opennode ``` **Purpose:** Override auto-detected webhook URL. Used as the callback URL for all payment providers (OpenNode and Strike). **Auto-detection order:** 1. `PaymentProvider:WebhookUrl` from config (if set) 2. `WEBSITE_HOSTNAME` environment variable (Azure App Service) 3. `APP_URL` environment variable (custom deployment) 4. `localhost:5096` (local development fallback) ### L402 Configuration #### Root Key ```bash L402_ROOT_KEY=your-secret-l402-root-key ``` **Purpose:** Secret key for macaroon signing. Required in production. :::caution In development, a default key is used. Always set this in production. ::: #### L402 Options (appsettings.json) ```json { "L402": { "Enabled": true, "ServiceName": "lightning-enable", "Location": "https://api.lightningenable.com", "DefaultPriceSats": 100, "InvoiceExpirySeconds": 600, "DefaultTokenValiditySeconds": 3600, "CacheVerifiedTokens": true, "TokenCacheSeconds": 300, "AllowLegacyLsatScheme": true, "MaxProxyRequestBodyBytes": 1048576, "MaxProxyResponseBodyBytes": 10485760, "ProtectedPaths": ["/api/l402/*"], "ExcludedPaths": ["/api/l402/pricing", "/api/l402/status"], "EndpointPricing": [ { "PathPattern": "/api/l402/demo", "PriceSats": 1, "ServiceName": "demo", "TokenValiditySeconds": 3600 } ] } } ``` | Setting | Default | Description | |---------|---------|-------------| | `Enabled` | true | Enable L402 authentication middleware | | `ServiceName` | lightning-enable | Service identifier in macaroons | | `Location` | - | Base URL for the service | | `DefaultPriceSats` | 100 | Default price in satoshis for unpriced endpoints | | `InvoiceExpirySeconds` | 600 | Lightning invoice expiry (10 min) | | `DefaultTokenValiditySeconds` | 3600 | Token validity period (1 hour) | | `CacheVerifiedTokens` | true | Cache verified tokens for performance | | `TokenCacheSeconds` | 300 | Token cache duration (5 min) | | `AllowLegacyLsatScheme` | true | Accept the legacy "LSAT" auth scheme in addition to "L402" | | `MaxProxyRequestBodyBytes` | 1048576 | Max request body size through L402 proxy (1 MB). Set to 0 to disable | | `MaxProxyResponseBodyBytes` | 10485760 | Max response body size through L402 proxy (10 MB). Set to 0 to disable | | `ProtectedPaths` | [] | Glob patterns for protected endpoints | | `ExcludedPaths` | [] | Glob patterns to exclude from L402 | | `EndpointPricing` | [] | Per-endpoint pricing rules (array of `PathPattern`, `PriceSats`, `ServiceName`, `TokenValiditySeconds`) | ### Email Configuration Lightning Enable sends transactional email (magic links, trial reminders, payment confirmations) via SMTP or Microsoft Graph, configured under the `Email` section (`Email__Provider`, `Email__SmtpHost`, etc. — see `EmailSettings`). #### Send Timeout ```bash Email__SmtpTimeoutSeconds=15 ``` **Purpose:** Bounds one email send — the whole SMTP connect + authenticate + send sequence, or the outbound Microsoft Graph API call — to a single budget. MailKit's own per-stage SMTP default is roughly 100 seconds, so a hung or unreachable mail server could otherwise hold a request thread for minutes across the three stages combined. Several sends happen inline on request paths (Stripe webhook handling, magic-link delivery), so a slow mail server could make an upstream caller like Stripe time out and retry. **Default:** `15` seconds. **Valid range:** `1`–`120`, validated at startup — an out-of-range value fails host startup rather than degrading silently. ### CORS Configuration ```json { "AllowedOrigins": [ "https://yourapp.com", "https://admin.yourapp.com" ] } ``` **Purpose:** Restrict browser-based API access to specific domains. - **Production:** List all legitimate client domains - **Development:** Automatically allows common localhost ports - **Empty array:** API not accessible from browsers Merchant-specific origins (e.g., Shopify storefronts) are additionally covered by the dynamic per-merchant origin registry — merchants don't need entries here. ### Logging Configuration ```json { "Serilog": { "MinimumLevel": { "Default": "Information", "Override": { "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "WriteTo": [ { "Name": "Console" }, { "Name": "File", "Args": { "path": "logs/lightning-enable-.txt", "rollingInterval": "Day" } } ] } } ``` ### Development Configuration For local development, use `appsettings.Development.json`: ```json { "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=LightningEnable;Trusted_Connection=True;" }, "AdminApiKey": "DEV-ADMIN-KEY-FOR-LOCAL-TESTING", "OpenNode": { "Environment": "dev" }, "Stripe": { "SecretKey": "sk_test_xxxxx", "PublishableKey": "pk_test_xxxxx", "WebhookSecret": "whsec_xxxxx", "BaseUrl": "http://localhost:5096" } } ``` Default encryption key in development: ``` DEV-ENCRYPTION-KEY-DO-NOT-USE-IN-PRODUCTION-12345678 ``` ### Production Checklist Before deploying to production, ensure: - [ ] `DB_ENCRYPTION_KEY` is set and backed up securely - [ ] `ADMIN_API_KEY` is set to a secure value - [ ] `ASPNETCORE_ENVIRONMENT=Production` - [ ] Database connection string configured for production SQL Server - [ ] `PaymentProvider__Default` set (`Strike` recommended) - [ ] `OpenNode:Environment=production` for mainnet (if any merchants use OpenNode) - [ ] Stripe live keys configured (`sk_live_...`, `pk_live_...`) - [ ] Stripe webhook endpoint created in Stripe Dashboard - [ ] `AllowedOrigins` restricted to legitimate domains - [ ] `L402_ROOT_KEY` set (if using L402) - [ ] SSL/TLS certificate configured - [ ] Database migrations applied ### Azure App Service Configuration When deploying to Azure App Service, set these application settings: | Setting Name | Value | |--------------|-------| | `ASPNETCORE_ENVIRONMENT` | `Production` | | `DB_ENCRYPTION_KEY` | (from Key Vault) | | `ADMIN_API_KEY` | (from Key Vault) | | `L402_ROOT_KEY` | (from Key Vault) | | `ConnectionStrings__DefaultConnection` | (Azure SQL connection) | | `Stripe__SecretKey` | (from Key Vault) | | `Stripe__PublishableKey` | `pk_live_...` | | `Stripe__WebhookSecret` | (from Key Vault) | | `Stripe__BaseUrl` | `https://api.lightningenable.com` | | `PaymentProvider__Default` | `Strike` | | `OpenNode__Environment` | `production` (if any merchants use OpenNode) | :::tip Use Key Vault References For sensitive values, use Azure Key Vault references: ``` @Microsoft.KeyVault(SecretUri=https://your-vault.vault.azure.net/secrets/DB-ENCRYPTION-KEY/) ``` ::: ## Next Steps - [Strike Setup](/strike-setup/account-setup) - Configure the recommended payment provider - [Quick Start](/getting-started/quick-start) - Test your configuration - [Webhooks](/api-reference/webhooks) - Set up webhook handling ============================================================================== # Legal Considerations Source: https://docs.lightningenable.com/configuration/legal-considerations ============================================================================== # Legal Considerations This document outlines the legal considerations, liability boundaries, and terms acknowledgment for using Lightning Enable's AI spending capabilities. ## Architecture Lightning Enable is **API middleware software** that connects your platform to payment providers (Strike, OpenNode). ### What Lightning Enable Does - Provides MCP tools that interact with your wallet - Forwards payment requests to your configured wallet provider - Enforces budget limits you configure - Tracks payment history for transparency ### What Lightning Enable Does NOT Do - Hold, custody, or control any Bitcoin (your payment provider does this) - Generate, store, or access private keys - Process payments (your payment provider does this) - Make spending decisions on your behalf - Guarantee payment success or reversal ### How It Works ``` Lightning Enable = API Middleware (connects your platform to Strike or OpenNode) Payment Provider = Payment Processor / Custodian (handles funds) You = Provider Account Holder ``` Lightning Enable forwards API requests to your configured payment provider (Strike or OpenNode). Your provider processes payments and handles custody. Consult with legal counsel regarding your specific regulatory obligations. ## Liability Boundaries ### User Responsibilities By using the `pay_invoice` tool, you acknowledge responsibility for: 1. **Authorizing AI Spending** - You are explicitly authorizing an AI agent to initiate payments - You understand the AI may make payments based on its interpretation of your requests - You accept that AI systems can make mistakes 2. **Configuration** - Setting appropriate budget limits - Using a dedicated wallet with limited funds - Securing your API keys - Monitoring spending activity 3. **Oversight** - Reviewing payments made by AI agents - Detecting and reporting unauthorized activity - Maintaining adequate supervision 4. **Funds** - All funds in your wallet are at risk - You accept potential loss up to your wallet balance - You will not hold Lightning Enable liable for spent funds ### Lightning Enable Does NOT Accept Liability For - Payments made by AI agents, intended or unintended - Misinterpretation of user intent by AI systems - Prompt injection or social engineering attacks - API key compromise or unauthorized use - Budget bypass exploits or software bugs - Third-party service failures (Strike, OpenNode, Lightning Network) - Loss of funds due to any cause - Consequential damages from AI spending activity ### Risk Acknowledgment Using `pay_invoice` involves inherent risks: | Risk | Description | |------|-------------| | AI Misinterpretation | AI may pay invoices you didn't intend | | Prompt Injection | Malicious content may trigger payments | | Key Compromise | Stolen API keys enable unauthorized spending | | Software Bugs | Edge cases may bypass budget limits | | Network Issues | Payments may fail or be delayed | | Irreversibility | Lightning payments cannot be reversed | ## Terms of Use ### Explicit Acknowledgment By configuring and using the `pay_invoice` tool, you explicitly acknowledge: 1. **Voluntary Authorization** - You are voluntarily authorizing automated spending - You can revoke this at any time by removing your API key - Continued use constitutes continued authorization 2. **Assumption of Risk** - You understand and accept all risks described in this document - You are using AI spending capabilities at your own risk - You have been warned and proceed with informed consent 3. **Budget Limits** - Budget limits are advisory safeguards, not guarantees - Edge cases, race conditions, or bugs may allow spending beyond limits - You accept this possibility and have limited your wallet balance accordingly 4. **No Recourse** - You waive claims against Lightning Enable for spent funds - You will not seek recovery from Lightning Enable for any loss - Your recourse is limited to your relationship with your payment provider (Strike or OpenNode) 5. **Age and Capacity** - You are of legal age to enter contracts in your jurisdiction - You have legal capacity to make financial decisions - You are not using this service where prohibited ### Jurisdictional Considerations - Bitcoin and Lightning Network may be regulated in your jurisdiction - AI spending may have tax implications - You are responsible for compliance with local laws - Lightning Enable makes no representations about legality in any jurisdiction ## Recommendations ### For Individual Users 1. **Use only discretionary funds** you can afford to lose completely 2. **Start with minimal amounts** (< $10 equivalent) 3. **Increase slowly** as you gain experience 4. **Never use** funds needed for bills, savings, or obligations 5. **Consult** a financial advisor if unsure ### For Businesses 1. **Create separate accounts** for AI spending 2. **Implement internal controls** and spending approval workflows 3. **Document** all AI spending authorization 4. **Audit** AI spending regularly 5. **Consult legal counsel** about liability exposure 6. **Consider insurance** for potential losses ### For Developers 1. **Test with testnet** before mainnet 2. **Use minimal budgets** in production 3. **Log all payment attempts** for debugging 4. **Handle failures gracefully** 5. **Implement circuit breakers** for anomalous spending ## Indemnification You agree to indemnify, defend, and hold harmless Lightning Enable, its officers, directors, employees, and agents from any claims, damages, losses, or expenses (including attorney fees) arising from: - Your use of AI spending tools - Payments made through your configured wallet - Violation of these terms - Unauthorized use of your API keys - Your negligence or willful misconduct ## Contact For questions about these legal considerations: - **General inquiries:** support@lightningenable.com - **Legal matters:** legal@lightningenable.com - **Security issues:** security@lightningenable.com ## Related Documentation - [AI Spending Security](/products/agentic-commerce/ai-spending-security) - Security best practices - [Spending Guidelines](/configuration/ai-spending-guidelines) - Budget recommendations - [Terms of Service](/legal/terms-of-service) - [Privacy Policy](/legal/privacy-policy) ============================================================================== # Design Philosophy Source: https://docs.lightningenable.com/design-philosophy ============================================================================== # Design Philosophy Lightning Enable is infrastructure. These principles guide its design. ## Non-Ideological We do not advocate for any monetary system, economic theory, or political position. Lightning Network is a technology. We use it because it provides millisecond settlement at negligible cost. If a better technology emerges, we will evaluate it on technical merits. ## Mechanical The system is deterministic. Same inputs produce same outputs. No human judgment in the critical path. No discretionary decisions about which settlements to process. ``` if valid_request: create_invoice() if valid_preimage: grant_access() ``` This is not a feature. It is a requirement for software that operates autonomously. ## We Never Touch Funds Lightning Enable never holds funds — your payment provider (Strike or OpenNode) facilitates custody and settlement. This is an architectural constraint that removes an entire category of failure modes: - No insolvency risk from us - Simplified regulatory posture - No counterparty risk from us - No key management complexity Custody belongs with entities designed for custody. Your payment provider is licensed, regulated, and designed for this responsibility. ## Boring Good infrastructure is boring. It works. It does not surprise you. It does not require attention. We optimize for: - Predictability over features - Stability over velocity - Simplicity over capability ## Protocol-Native We build on open protocols (Lightning Network, L402) rather than proprietary systems. This ensures: - No vendor lock-in - Interoperability with the broader ecosystem - Long-term sustainability independent of any single company ## Constraints We Accept These are intentional limitations: | Constraint | Rationale | |------------|-----------| | Lightning-first | Lightning is the settlement rail. Providers may also supply an on-chain fallback address (`onchainAddress` on invoices) for wallets that cannot pay Lightning | | Payment provider dependency | Outsource custody to specialists (Strike or OpenNode) | | No transaction fees | Removes incentive misalignment | | One codebase, plan-based capabilities | Every merchant runs the same service; what differs is which capabilities your [subscription plan](/products/product-overview) enables — never a bespoke build | ## What We Do Not Do - Process payments (your payment provider does this) - Hold funds (your payment provider does this) - Make decisions about settlements (the protocol does this) - Provide financial advice (lawyers and accountants do this) - Evangelize (marketing does this) We are plumbing. The water flows through us. We do not own the water. ## Further Reading - [Core Concepts](/concepts) - Foundational concepts - [Architecture](/) - System architecture ============================================================================== # Economic Patterns Source: https://docs.lightningenable.com/economic-patterns ============================================================================== # Economic Patterns Per-request economics require infrastructure that legacy rails cannot provide. ## The Granularity Problem Traditional payment rails have minimum viable transaction sizes: | Rail | Minimum Practical | Latency | |------|------------------|---------| | Credit Card | $0.50-1.00 | 2-3 seconds | | ACH | $1.00+ | 1-3 days | | Wire | $25.00+ | Hours | | PayPal | $0.35+ | Seconds | Lightning Network: | Rail | Minimum Practical | Latency | |------|------------------|---------| | Lightning | $0.001 | Milliseconds | This is not an incremental improvement. It is a categorical difference. ## Request-Level Economics When settlement cost approaches zero and latency approaches zero, new patterns become viable: ### Pay-per-Request APIs Instead of monthly subscriptions with usage limits: ``` POST /api/analyze Settlement: 21 sats ($0.02) Response: { analysis: "..." } ``` No accounts. No overages. No reconciliation. ### Agent Budgets Autonomous agents can operate with bounded budgets: ``` Agent Budget: 10,000 sats Each action: 10-100 sats Total actions: 100-1000 ``` The budget is a hard constraint, not an estimate. ### Metered Compute Compute resources can be priced at actual usage: ``` CPU-second: 1 sat GPU-second: 10 sats Storage-MB-month: 0.1 sats ``` No minimum commitments. No reserved capacity. ## Why This Matters for Software Software operates at scales incompatible with human-oriented payment rails: - An API might serve 1M requests/day - An agent might take 10,000 actions/task - A mesh network might have 100,000 nodes These cannot each have accounts, invoices, and reconciliation processes. They need settlement as a primitive. ## The Settlement Layer Lightning Enable provides this settlement layer. Not payment processing. Not financial services. Just the infrastructure that makes request-level economics possible. ## Further Reading - [L402 Protocol](/products/agentic-commerce/overview) - HTTP-native per-request settlement - [AI Agent Integration](/products/agentic-commerce/ai-agent-integration) - Agent budget patterns - [Core Concepts](/concepts) - Foundational concepts ============================================================================== # FAQ Source: https://docs.lightningenable.com/faq ============================================================================== # Frequently Asked Questions ## General ### What is Lightning Enable? Lightning Enable is payment enablement middleware that connects your platform to payment providers (Strike, OpenNode) for Bitcoin Lightning payments. We provide the API layer - your payment provider handles payment processing, custody, and settlement. ### How is Lightning Enable different from a payment gateway? | Aspect | Traditional Gateway | Lightning Enable | |--------|---------------------|------------------| | Role | Processes payments | API middleware | | Fund custody | Gateway holds funds | Provider holds funds | | KYB/Compliance | Gateway handles | Provider handles | | Settlement | Gateway settles | Provider settles | | Revenue model | Per-transaction fees | Subscription only | ### Why do I need a payment provider account? Lightning Enable connects to a payment provider (Strike or OpenNode) that facilitates: - Bitcoin custody and settlement - KYB/KYC/AML compliance - Bank settlements (if desired) - Lightning Network infrastructure Lightning Enable connects to your provider account to create invoices and receive payment notifications. Strike is the default and recommended for most merchants. ### What is Lightning Enable's role in payment processing? Lightning Enable is API middleware software. We connect your platform to a payment provider (Strike or OpenNode). Lightning Enable does not: - Hold or custody funds (your provider does) - Process payments (your provider does) - Handle settlements (your provider does) Your payment provider handles payment processing, custody, KYB/KYC, and settlement. Consult with legal counsel regarding your specific regulatory obligations. ## Pricing ### How much does Lightning Enable cost? | Plan | Price | Includes | |------|-------|----------| | Free Producer Sandbox | Free — no card | 3 endpoints, 200 challenges/month, 1,000 sats max per challenge | | Agentic Commerce | $49/month | Full REST API + L402 protocol + settlement via Strike API | | Agentic Commerce — Business | Contact us | Full REST API + pay-per-request API monetization + white-glove onboarding | Agentic Commerce and Agentic Commerce — Business both include webhook notifications, multi-currency pricing, priority email support, and access to all platform integrations (Shopify Commerce). Agentic Commerce also includes a **30-day free trial** through self-serve checkout; Business is contact-only (email support@lightningenable.com — it isn't purchasable through `/Checkout`), and any trial terms are arranged directly. ### Are there transaction fees? Lightning Enable charges a flat subscription fee only — no per-transaction fees from us. Your payment provider may charge their own processing fees, which you pay directly to them. ### Can I switch plans? Yes. Upgrade or downgrade anytime. Changes take effect immediately, with prorated billing. ## Technical ### What programming languages are supported? Lightning Enable provides a REST API that works with any language. We have examples for: - JavaScript/TypeScript - C# / .NET - Python - PHP - Ruby ### Do you have an SDK? Yes. L402 HTTP client libraries are available for auto-paying L402-protected APIs: - **Python:** [`l402-requests`](/tools/l402-requests) — three lines of code, paid APIs just work - **.NET:** [`L402Requests`](/tools/l402-dotnet) — same experience for .NET - **TypeScript/npm:** [`l402-requests`](/tools/l402-ts) — same experience for TypeScript See the [Developer Tools](/tools/l402-requests) section for installation and usage guides. For the Lightning Enable REST API itself, use your preferred HTTP client — we have examples for JavaScript, C#, Python, PHP, and Ruby. ### What's the API latency? Typical response times: - Create payment: 200-500ms - Get payment status: 50-100ms Most latency comes from provider API calls (Strike or OpenNode). ### Is there a sandbox environment? Yes. Both providers offer test environments: **Strike sandbox:** - Dashboard: dashboard.dev.strike.me - API: api.dev.strike.me **OpenNode testnet:** - Dashboard: app.dev.opennode.com - API: dev-api.opennode.com No KYB/KYC required for test environments. ## Payments ### How long do invoices last? Default: 600 seconds (10 minutes). After expiration, the invoice cannot be paid and you'll need to create a new one. ### What currencies are supported? - **USD** - US Dollar - **EUR** - Euro - **GBP** - British Pound - **BTC** - Bitcoin (decimal, up to 8 places — use BTC for satoshi precision) Currency codes are uppercase 3-letter values; fiat currencies are converted to Bitcoin at current rates. Both methods require sufficient balance in your provider account. ### What happens if a payment fails? Lightning payments either succeed instantly or fail immediately. If a customer's payment fails: - No funds are transferred - Invoice can be retried - Create a new invoice if expired ## Webhooks ### How do I know when a payment is complete? Two options: 1. **Webhooks (recommended)** - Receive instant notifications 2. **Polling** - Check payment status via API Webhooks are more efficient and provide real-time updates. ### Are webhooks retried? Yes — failed deliveries are retried with exponential backoff (30s, 60s, 120s, 240s, 480s — 5 retry attempts over ~16 minutes), with identical payload bytes on every attempt (signatures are freshly timestamped per attempt — dedupe on payload content, never on the signature header). After retries exhaust, the event is marked permanently failed — recover by polling `GET /api/payments/{invoiceId}` or forcing a provider re-check with `POST /api/payments/{invoiceId}/sync`. ### How do I verify webhooks? Webhooks include an HMAC-SHA256 signature in the `X-LightningEnable-Signature` header in the format `t={timestamp},v1={hmac}`. Verify by computing HMAC-SHA256 of `{timestamp}.{payload}` using your webhook secret, and reject signatures older than 5 minutes. See the [Webhook Verification guide](/api-reference/webhooks#verifying-webhooks) for code examples. ## L402 Protocol ### What is L402? L402 is a protocol for HTTP 402 (Payment Required) that enables pay-per-request API access using Lightning payments. Perfect for: - API monetization - Premium content access - Metered API usage ### Do I need L402? L402 is optional. Use it if you want: - Per-request payment instead of subscriptions - Anonymous API access without accounts - Micropayment monetization ### Which plan includes L402? L402 server-side features (creating L402-protected endpoints for your API) are available on both Agentic Commerce plans: | Plan | L402 Server-Side | Price | |------|------------------|-------| | Free Producer Sandbox | ✅ Yes (3 endpoints, capped monthly volume, 1,000 sats max/challenge) | Free — no card | | Agentic Commerce | ✅ Yes | $49/mo | | Agentic Commerce — Business | ✅ Yes | Contact us | Note: The MCP server's L402 *client* tools (`access_l402_resource`, `pay_l402_challenge`) are free for everyone. The subscription is only needed to *create* L402-protected endpoints. ### How do I check if my account has L402 enabled? Use the `/api/merchant/l402-status` endpoint: ```bash curl https://api.lightningenable.com/api/merchant/l402-status \ -H "X-API-Key: your-api-key" ``` Response shows `l402Enabled: true` if your plan includes L402 features. ### Can I monetize third-party APIs? Yes. The L402 proxy feature lets you wrap any API with Lightning payments. You pay the upstream API, customers pay you. ### Where is L402 used in production? [store.lightningenable.com](https://store.lightningenable.com) uses L402 payments for its Community Collection feature, where designers pay a Lightning micropayment to submit designs to the store. This demonstrates L402 as a gating mechanism for real-world commerce workflows. ## MCP Server (AI Integration) ### What is the MCP server? The MCP (Model Context Protocol) server enables AI agents like Claude to automatically pay for L402-protected APIs using Lightning payments. It connects to your wallet and handles payments on behalf of the AI. ### Are all MCP tools free? The MCP server is open-source (MIT) and free to install. Wallet, invoice, L402, budget, and API-discovery tools work out of the box with just a wallet. `l402_producer` (sell access via L402) and four of `agent_services`'s seven actions (`request`, `publish`, `unpublish`, `attest`) unlock with a Lightning Enable API key; the other `agent_services` actions (`discover`, `settle`, `reputation`) work with just a wallet. See the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the full tool list. ### What is LIGHTNING_ENABLE_API_KEY? This environment variable is your merchant API key for the MCP server. It is **required** for every `l402_producer` action (formerly the separate `create_l402_challenge` / `verify_l402_payment` tools) and the `agent_services` actions `request`/`publish`/`unpublish`/`attest` (formerly `request_agent_service` / `publish_agent_capability` / `unpublish_agent_capability` / `publish_agent_attestation`), which authenticate with the Lightning Enable API. `agent_services`'s `discover`/`settle`/`reputation` actions (formerly `discover_agent_services` / `settle_agent_service` / `get_agent_reputation`) — and all the out-of-the-box tools — work without it. ```json { "mcpServers": { "lightning-enable": { "env": { "NWC_CONNECTION_STRING": "nostr+walletconnect://...", "LIGHTNING_ENABLE_API_KEY": "your-merchant-api-key" } } } } ``` For the out-of-the-box tools, just configure a wallet — no API key needed. ### Do I need a subscription to use MCP tools? For the out-of-the-box tools (payment, wallet, L402 client), no subscription or API key is required — just configure a wallet. The producer tools and the ASA request/publish/unpublish tools require a Lightning Enable API key (`LIGHTNING_ENABLE_API_KEY`); ASA discovery, settlement, and reputation reads work with just a wallet. The MCP server itself is open-source (MIT) and free to install. ### What happened to the license purchase? Removed in v1.6.0. Previously, L402 tools (`access_l402_resource`, `pay_l402_challenge`) required a 6,000 sat license or a paid monthly subscription. The consumer/L402 client tools are now free with no license required. The producer tools and the ASA request/publish/unpublish tools still require a Lightning Enable API key (`LIGHTNING_ENABLE_API_KEY`); ASA discovery, settlement, and reputation reads — plus all the out-of-the-box tools — need no key. ### How do I test L402 payments? The [Lightning Enable Store](https://store.lightningenable.com) is a live L402-powered web store where AI agents can purchase physical merchandise. Ask Claude: ``` Buy me a Lightning Enable t-shirt from store.lightningenable.com ``` This demonstrates the full L402 flow: browse catalog, checkout (get 402), pay invoice, claim with L402 credential. ## API Features ### What is the X-Idempotency-Key header and when should I use it? The `X-Idempotency-Key` header prevents duplicate operations when retrying requests. If a network failure occurs and you're unsure whether your request succeeded, resending with the same idempotency key returns the original response instead of creating a duplicate. **How it works:** 1. Generate a unique key (UUID recommended) and include it as `X-Idempotency-Key: ` 2. If the request succeeds, the response is cached for 24 hours keyed to your merchant account 3. Repeating the request with the same key returns the cached response with an `X-Idempotency-Replayed: true` header **Supported endpoints:** - `POST /api/payments` (create payment) - `POST /api/refunds` (create refund) - `POST /api/checkout/sessions` (create checkout session) **Example:** ```bash curl -X POST https://api.lightningenable.com/api/payments \ -H "X-API-Key: your-api-key" \ -H "X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \ -H "Content-Type: application/json" \ -d '{"amount": 10.00, "currency": "USD"}' ``` **Rules:** - Keys must be 256 characters or fewer - Keys are scoped per merchant, so different merchants can use the same key without conflict - Only successful responses (2xx) are cached - Use a new key for each distinct operation ### How do I trace a request through logs? Every API request is assigned a **correlation ID** for end-to-end tracing. You can use this ID when contacting support to quickly locate your request in our logs. **How it works:** - Send your own ID via the `X-Correlation-Id` request header, or the API generates one automatically - The same ID is returned in the `X-Correlation-Id` response header - All internal log entries for that request include the correlation ID **Example:** ```bash # Let the API generate one curl -v https://api.lightningenable.com/api/payments \ -H "X-API-Key: your-api-key" # Response header: X-Correlation-Id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 # Or provide your own curl -v https://api.lightningenable.com/api/payments \ -H "X-API-Key: your-api-key" \ -H "X-Correlation-Id: my-trace-id-12345" ``` If something goes wrong, include the correlation ID when contacting support for faster diagnosis. ### Why am I getting a 429 Too Many Requests response? The API enforces rate limits to protect against abuse. If you exceed the limit, you'll receive a `429 Too Many Requests` response. **Rate limits by operation type:** | Operation | Limit | Window | |-----------|-------|--------| | Global (all requests) | 100 requests | 1 minute | | Payment/refund creation | 10 requests | 1 minute | | Checkout session creation | 5 requests | 1 minute | | Read operations (GET) | 200 requests | 1 minute | | Write operations (updates) | 20 requests | 1 minute | | Admin operations | 30 requests | 1 minute | | Webhooks | 100 requests | 1 minute (sliding window) | **Rate limits are partitioned by:** - API key (if authenticated) - IP address (if unauthenticated) **How to handle 429 responses:** 1. Implement exponential backoff (wait 1s, 2s, 4s, etc.) 2. Cache responses for repeated queries instead of re-requesting 3. Use webhooks for payment status instead of polling 4. Batch operations where possible ### What are the L402 proxy size limits? The L402 reverse proxy enforces size limits on both request and response bodies to prevent abuse: | Direction | Default Limit | |-----------|--------------| | Request body (client to proxy) | 1 MB (1,048,576 bytes) | | Response body (target API to client) | 10 MB (10,485,760 bytes) | - **Request too large:** Returns `413 Payload Too Large` - **Response too large:** Returns `502 Bad Gateway` These limits apply to proxied API calls only. Standard Lightning Enable API endpoints are not affected. ### What security protections does the L402 proxy have? The L402 proxy includes several security layers to protect both API providers and consumers: **SSRF (Server-Side Request Forgery) Protection:** - Target URLs are validated at request time by resolving DNS and checking the resulting IP addresses - Requests to private/internal IP ranges are blocked: `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `::1`, `fc00::/7`, `fe80::/10` - Cloud metadata endpoints (e.g., `169.254.169.254`) are blocked to prevent credential theft **Header Sanitization:** - Sensitive hop-by-hop headers are stripped from forwarded requests (`Authorization`, `Proxy-Authorization`, `Connection`, etc.) - The proxy adds identification headers (`X-L402-Proxy-Id`, `X-L402-Proxy`) to proxied responses **Request Limits:** - Request and response body size limits (see "What are the L402 proxy size limits?") - 30-second timeout for upstream API responses (returns `504 Gateway Timeout` if exceeded) **Callback URL Validation:** - Webhook callback URLs are validated against blocked hostnames (localhost, metadata services, Kubernetes internals) - Common internal service ports (Redis 6379, PostgreSQL 5432, MySQL 3306, etc.) are blocked **Error Sanitization:** - In production, error responses are sanitized to remove stack traces, internal namespaces, file paths, and connection strings - Errors include a correlation ID for support reference without exposing implementation details ### How does error sanitization work in production? In production, the API automatically strips internal details from error responses to prevent information leakage. Fields like `stackTrace`, `innerException`, and `exceptionType` are removed. Fields like `message` and `details` are replaced with a generic message if they contain references to internal namespaces, file paths, or database details. Every error response includes a `correlationId` that you can reference when contacting support. In development environments, full error details (including stack traces) are returned for debugging convenience. ## Security ### Is my API key secure? Yes. API keys are encrypted at rest using AES-256-GCM. They're only decrypted in memory during authentication. ### Should I use HTTPS? Yes. Always use HTTPS in production. We require HTTPS for webhooks in production environments. ### What if my API key is compromised? 1. Generate a new API key immediately 2. Update your application configuration 3. The old key is automatically invalidated 4. Review your access logs ## Troubleshooting ### Payments not working 1. **Check API key** - Is it valid and for the correct environment? 2. **Check provider** - Is your payment provider account (Strike or OpenNode) active and verified? 3. **Check environment** - Are you using sandbox/testnet for testing? 4. **Check logs** - What error is returned? ### Webhooks not received 1. **Check URL** - Is your webhook URL correct and HTTPS? 2. **Check firewall** - Can external services reach your endpoint? 3. **Check logs** - View webhook delivery logs 4. **Test manually** - Use curl to test your endpoint ### Invalid API key errors 1. Verify key is copied correctly (no extra spaces) 2. Confirm environment matches (dev vs production) 3. Check key hasn't been revoked 4. Generate new key if needed ### Rate limit exceeded 1. Implement request caching 2. Use webhooks instead of polling 3. Add exponential backoff 4. Review the rate limits table in ["Why am I getting a 429 Too Many Requests response?"](#why-am-i-getting-a-429-too-many-requests-response) for specific limits per endpoint ## Support ### How do I get help? - **Documentation:** You're reading it! - **Email:** support@lightningenable.com - **Enterprise:** Contact support@lightningenable.com ### Is there an SLA? All plans include best-effort availability and priority email support. We do not currently offer formal SLA guarantees. ### Where can I report bugs? Email support@lightningenable.com with: - Description of the issue - Steps to reproduce - Error messages/logs - Your environment details ## Still Have Questions? If your question isn't answered here: 1. Check the [API Reference](/api-reference/overview) 2. Review the [Product Guides](/products/product-overview) 3. Email support@lightningenable.com We're here to help you succeed with Lightning payments! ============================================================================== # Activate with Lightning (L402 Fast Lane) Source: https://docs.lightningenable.com/getting-started/activate-with-lightning ============================================================================== # Activate with Lightning (L402 Fast Lane) **L402 Fast Lane** lets you create a Lightning Enable account and start a full 30-day **Agentic Commerce** trial by paying a single **100-sat L402 challenge** — no credit card. The signup itself runs on L402: `402 → pay → macaroon proof → account activated`. You use the exact payment flow your own APIs and agents will use. :::note Not the same as "Pay with Bitcoin — 10% off" That option pays a *subscription* with Bitcoin for a discount. **Fast Lane** is a *no-card trial* activated with a tiny Lightning payment — two different things. ::: :::tip What you'll need - A funded Lightning wallet with a little more than 100 sats (to cover the routing fee). L402 requires a wallet that surfaces the payment **preimage** — **LND, Strike, or an NWC wallet** (CoinOS, Alby Hub, CLINK). OpenNode and Primal NWC don't surface preimages and won't work here. - About a minute. ::: ## The fastest path: the MCP tool If you run the [Lightning Enable MCP](/products/agentic-commerce/mcp-quickstart) with a connected wallet, one tool call does everything: ```text create_lightning_enable_account(email="you@example.com") ``` It pays the 100-sat challenge, returns your merchant API key and trial details, and writes the key into `~/.lightning-enable/config.json` so the API-key-gated producer tools unlock on the next restart. Requires MCP **≥ 1.15.0** (`dotnet tool update -g LightningEnable.Mcp` or `pip install -U lightning-enable-mcp`). ## Dev CLI: l402-requests (Python) Prefer a script? The [`l402-requests`](/tools/l402-requests) client auto-detects your wallet and handles the whole `402 → pay → retry` handshake, preserving your email through the paid retry: ```python # pip install "l402-requests>=0.5.0" # 0.5.0 adds NIP-44 NWC support (Alby Hub, etc.) from l402_requests import L402Client client = L402Client() # auto-detects LND > NWC > Strike > OpenNode from env / config resp = client.post( "https://api.lightningenable.com/api/signup/l402", json={"email": "you@example.com"}, timeout=60, ) data = resp.json() print("API key: ", data["apiKey"]) # save this — it's shown once print("Trial ends:", data["trialEndsAt"]) print("Dashboard: ", data["dashboardUrl"]) ``` The [.NET](/tools/l402-dotnet) and [TypeScript](/tools/l402-ts) clients do the same handshake — point them at the same endpoint with a JSON `{ "email": "…" }` body. ## Raw protocol (any language) Before spending anything, you can fetch a read-only quote — `GET https://api.lightningenable.com/api/signup/l402` returns availability, the current price and trial terms, required fields, and the exact flow, with no side effects. The endpoint itself is a standard two-phase L402 flow, so you can drive it by hand: 1. **Ask for the challenge** — `POST /api/signup/l402` with body `{"email":"you@example.com"}`. You get `402 Payment Required` with a `WWW-Authenticate: L402 macaroon="…", invoice="…"` header. 2. **Pay the invoice** with any preimage-surfacing wallet to obtain the preimage. 3. **Complete** — repeat the POST with the *same* body plus `Authorization: L402 :`. You get `200 OK` and your account: ```json { "status": "created", "apiKey": "lgw_…", "merchantId": 123, "email": "you@example.com", "planTier": "individual", "subscriptionStatus": "trialing", "trialEndsAt": "2026-08-06T00:00:00Z", "dashboardUrl": "https://api.lightningenable.com/dashboard", "nextSteps": [ { "id": "store_api_key", "title": "Store your API key", "detail": "…", "url": "https://api.lightningenable.com/dashboard/settings" }, { "id": "connect_payment_provider", "title": "Connect a payment provider", "detail": "…", "method": "PUT", "endpoint": "/api/merchant/strike-key", "url": "https://api.lightningenable.com/dashboard/login", "docs": "https://docs.lightningenable.com/strike-setup/account-setup" }, { "id": "mint_first_challenge", "title": "Mint your first L402 challenge", "detail": "…", "method": "POST", "endpoint": "/api/l402/challenges", "docs": "https://docs.lightningenable.com/products/agentic-commerce/producer-api-reference" }, { "id": "trial", "title": "Manage your trial", "detail": "…", "url": "https://api.lightningenable.com/dashboard" }, { "id": "docs", "title": "Read the full guide", "detail": "…", "url": "https://docs.lightningenable.com/getting-started/activate-with-lightning", "docs": "https://api.lightningenable.com/llms.txt" } ], "instructions": "Your Lightning Enable account is ready. 1) Store your API key now — it is shown only in this response… (full plain-text walkthrough of the same five steps)" } ``` `nextSteps` is an ORDERED array — always these five ids, in this order — for a caller that wants to branch on structured data. `instructions` renders the same guidance as one plain-text paragraph for a caller that only relays text. Every number in both (the trial price, the Free Producer Sandbox caps) is read live from the plan configuration, so it can never drift from what the account is actually on. A welcome email is also sent to the signup address at this point — see below. ## Semantics worth knowing (all activation paths) These apply whether you used the MCP tool, the Python client, or the raw protocol: - **Enumeration-safe by design:** the challenge phase never reveals whether an email is registered — an already-registered email mints an identical 402. If the email already has an account, the **paid** verify returns `409 merchant_exists` and the ~100-sat fee is not refunded, so confirm the email is correct and new before paying. Agents: always use the operator's real email, never an invented one. - **Verify promptly after paying.** The credential expires (about an hour). A late verify returns `401` — indistinguishable from an invalid token — and re-POSTing mints a **new payable invoice**; the first payment is not automatically recovered. If you paid, complete the retry right away. An unpaid or abandoned challenge never creates an account, and an unpaid `401` costs nothing — just re-POST for a fresh challenge. - **The API key is shown once** in the success response. It is recoverable later by signing in at the dashboard via a magic-link email to your signup address — so the email must be real and reachable. (Regenerating the key from Settings invalidates the old one immediately.) - **A welcome email is sent** to the signup address right after the account is created — the same guidance as the response's `nextSteps`/`instructions`, written for the human behind the agent. It never contains the API key or the L402 macaroon. Sending is best-effort: a delivery problem never fails or delays the signup response, and it is sent exactly once per created account (not on a `409 merchant_exists` retry). ## First success after activation Prove the account works end-to-end for about 1 sat, without building an API first: 1. Save the returned `apiKey` securely. 2. **Connect your payment provider** — add your [Strike](https://docs.lightningenable.com/strike-setup/account-setup) (recommended) or OpenNode API key in the [dashboard](https://api.lightningenable.com/dashboard) Settings. Challenges settle to *your* provider account; until one is connected, challenge creation returns `400`. 3. Create a 1-sat challenge: `POST /api/l402/challenges` (or the `l402_producer` MCP tool with `action="create"`) with any resource name and `priceSats: 1`. 4. Pay your own challenge with your wallet (`pay_l402_challenge`). 5. Verify it: `POST /api/l402/challenges/verify` (or `l402_producer` with `action="verify"`) — a `valid: true` response confirms your producer account is fully provisioned. ## What you get - A **30-day Agentic Commerce** trial: unlimited L402 endpoints, per-endpoint pricing, and the live dashboard with a per-request payment feed. - Your **merchant API key** — save it, it's shown once. It unlocks the [L402 Producer API](/products/agentic-commerce/l402-producer-api) and the API-key-gated MCP producer/ASA tools. - Sign in anytime at the [dashboard](https://api.lightningenable.com/dashboard). ## After the trial Add billing to keep Individual features, or do nothing and your account automatically moves to the **Free Producer Sandbox** (3 endpoints, limited monthly challenge volume, 1,000-sat max challenge price). Either way you keep your account and endpoints — prefer the no-code path? Start the Sandbox directly at [api.lightningenable.com/dashboard/signup](https://api.lightningenable.com/dashboard/signup). The Sandbox's **3 endpoints** means 3 *distinct resource paths*, counted for the life of the account — unlike the monthly challenge volume, this tally doesn't reset. Reuse your paths while experimenting rather than minting a fresh one each time. If a throwaway or test path ends up permanently holding a slot, email [support@lightningenable.com](mailto:support@lightningenable.com) and we can retire it to free the slot. Paying with an email that already belongs to an account won't create a second one — the paid verify returns `409 merchant_exists` and the activation fee is not refunded, so [sign in](https://api.lightningenable.com/dashboard) instead of paying again. ## Next step: sell with your agent You have an API key and a producer account — the next step is turning that into a paid endpoint. **[Sell With Your Agent](./sell-with-your-agent)** picks up right here: give your agent a receive wallet, a spend ceiling, and the API you want to charge for, and the [`producer-setup` skill](https://github.com/refined-element/lightning-enable-skills/tree/main/skills/producer-setup) wires up the whole path — proxy, manifest, and a self-test payment that proves it works — with no dashboard clicking required. --- *Lightning Enable is API middleware and never holds your funds. Your wallet pays the challenge directly, and your chosen settlement provider (Strike or OpenNode) custodies every payment on the APIs you monetize.* ============================================================================== # The Lightning Enable Ecosystem Source: https://docs.lightningenable.com/getting-started/ecosystem ============================================================================== # The Lightning Enable Ecosystem **Lightning Enable — infrastructure for agent commerce over Lightning.** Lightning Enable is a suite of open-source tools and a commerce orchestration layer that gives platforms, developers, and AI agents everything they need to transact over the Lightning Network using the L402 protocol. ## Architecture ``` +-------------------------------+ | Payment Providers | | Strike . OpenNode | | custody / settlement / KYB | +---------------+---------------+ | +---------------+---------------+ | Lightning Enable API | | api.lightningenable.com | | orchestration / L402 proxy | | / webhooks | +---------------+---------------+ | +-------------------------+-------------------------+ | | | +-------+--------+ +-------+--------+ +-------+--------+ | MCP Server | | L402 HTTP | | Agent Commerce | | AI agent tools | | Clients | | Store | | NuGet / PyPI / | | Python .NET TS | | API marketplace| | Docker | | (consumer) | | L402 data APIs | +-------+--------+ +----------------+ +----------------+ | +-------+--------+ +---------------------------------------+ | NostrWolfe | | L402 Server SDKs (producer side) | | Agent relay | | l402-server, l402-express (npm) | | wss://agents. | | L402Server, L402Server.AspNetCore | | lightningenable| | (NuGet) - charge for your own API | | .com | | on your own domain | +----------------+ +---------------------------------------+ ``` ## Components ### Agentic Commerce Platform The commerce orchestration layer that connects platforms to payment providers (Strike, OpenNode) for Bitcoin Lightning payments. Handles invoices, webhooks, L402 proxy, and merchant management. - **Live API:** [api.lightningenable.com](https://api.lightningenable.com) - **Docs:** [docs.lightningenable.com](https://docs.lightningenable.com) - **Get started:** [Quick Start Guide](/getting-started/quick-start) Lightning Enable does not hold funds — the configured payment provider facilitates custody and settlement. ### MCP Server — AI Agent Tools Open-source (MIT) MCP server that gives AI agents a Lightning wallet — wallet, invoice, and L402 tools free out of the box, plus producer and Agent Service Agreement tools with a Lightning Enable API key. See the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the full tool list. - **GitHub:** [refined-element/lightning-enable-mcp](https://github.com/refined-element/lightning-enable-mcp) - **NuGet:** [LightningEnable.Mcp](https://www.nuget.org/packages/LightningEnable.Mcp) - **PyPI:** [lightning-enable-mcp](https://pypi.org/project/lightning-enable-mcp) - **Docker Hub:** [refinedelement/lightning-enable-mcp](https://hub.docker.com/r/refinedelement/lightning-enable-mcp) - **Docs:** [MCP Quick Start](/products/agentic-commerce/mcp-quickstart) ```bash # Install in one command dotnet tool install -g LightningEnable.Mcp # .NET pip install lightning-enable-mcp # Python (add [nwc] extra for Nostr Wallet Connect wallets) docker pull refinedelement/lightning-enable-mcp # Docker ``` ### L402 HTTP Clients — Auto-Paying Libraries Open-source (MIT) drop-in HTTP clients that handle L402 payment flows automatically. Three lines of code — paid APIs just work. | Language | Package | Install | |----------|---------|---------| | **Python** | [l402-requests](https://pypi.org/project/l402-requests) | `pip install l402-requests` | | **.NET** | [L402Requests](https://www.nuget.org/packages/L402Requests) | `dotnet add package L402Requests` | | **TypeScript** | [l402-requests](https://www.npmjs.com/package/l402-requests) | `npm install l402-requests` | - **Python docs:** [l402-requests](/tools/l402-requests) - **.NET docs:** [L402Requests](/tools/l402-dotnet) - **TypeScript docs:** [l402-requests](/tools/l402-ts) ### L402 Server SDKs — Charge for Your API (Producer Side) Open-source (MIT) server SDKs and framework middleware for the **producer** side of L402: drop one line of middleware into your existing API and charge per request over Lightning. Native mode — your API stays on your own domain, and traffic never flows through Lightning Enable. | Stack | SDK | Framework middleware | Install | |-------|-----|---------------------|---------| | **Node.js** | [l402-server](https://www.npmjs.com/package/l402-server) | [l402-express](https://www.npmjs.com/package/l402-express) (Express) | `npm install l402-express` | | **.NET** | [L402Server](https://www.nuget.org/packages/L402Server) | [L402Server.AspNetCore](https://www.nuget.org/packages/L402Server.AspNetCore) (ASP.NET Core) | `dotnet add package L402Server.AspNetCore` | - **Docs:** [Native Integration overview](/products/agentic-commerce/native-integration) · [Express walkthrough](/products/agentic-commerce/native-integration-express) · [ASP.NET Core walkthrough](/products/agentic-commerce/native-integration-aspnet) **Live reference apps** — open-source paid APIs you can curl to see a 402 in your terminal: - **Express + Node:** [refined-element/l402-example-node](https://github.com/refined-element/l402-example-node) - **ASP.NET Core:** [refined-element/l402-example-aspnet](https://github.com/refined-element/l402-example-aspnet) ### Claude Skills — Ready-Made Agentic Commerce Skills Open-source Claude Skills for agentic commerce over Lightning + L402, powered by the Lightning Enable MCP server. Runs in Claude Code and Claude Desktop. - **GitHub:** [refined-element/lightning-enable-skills](https://github.com/refined-element/lightning-enable-skills) - Requires the [MCP server](#mcp-server--ai-agent-tools) with a configured wallet ### Research Agent Demo — Agent Buys Its Own Data Reproducible demo of end-to-end agent commerce: an AI agent buys its own research data over Lightning (L402) and writes a cited paper — about 20 sats, no API keys, no accounts. - **GitHub:** [refined-element/l402-research-agent](https://github.com/refined-element/l402-research-agent) ### Agent Commerce Store — L402 API Marketplace Live marketplace of dozens of L402 data APIs payable via Lightning micropayments — weather, research papers, SEC filings, economic data, and more, all accessible by AI agents. See the live manifest for the current list. - **Live:** [agent-commerce.store](https://agent-commerce.store) - **Manifest:** [L402 Manifest](https://agent-commerce.store/.well-known/l402-manifest.json) - **Machine docs:** [llms.txt](https://agent-commerce.store/llms.txt) ### Lightning Enable Store — Live L402 Demo Physical merch store demonstrating L402 checkout. AI agents can browse, pay via Lightning, and receive real products. - **Live:** [store.lightningenable.com](https://store.lightningenable.com) - **Machine docs:** [llms-full.txt](https://store.lightningenable.com/llms-full.txt) ### NostrWolfe — Agent Discovery via Nostr Open protocol enabling AI agents to discover each other, request services, and settle via the Lightning Network (L402) over Nostr. - **Website:** [nostrwolfe.com](https://nostrwolfe.com) - **Agent Relay:** `wss://agents.lightningenable.com` - **SDKs:** [le-agent-sdk-python](https://github.com/refined-element/le-agent-sdk-python) (Python) · [le-agent-sdk-ts](https://github.com/refined-element/le-agent-sdk-ts) (TypeScript) · [le-agent-sdk-dotnet](https://github.com/refined-element/le-agent-sdk-dotnet) (.NET) ## Start Here If You Want To... | Goal | Start here | |------|-----------| | **Accept Lightning payments on your platform** | [Quick Start Guide](/getting-started/quick-start) | | **Give your AI agent a Lightning wallet** | [MCP Quick Start](/products/agentic-commerce/mcp-quickstart) | | **Monetize your API with pay-per-request** | [API Monetization](/products/agentic-commerce/api-monetization) | | **Have an agent set up a paid endpoint for you, hands-free** | [Sell With Your Agent](/getting-started/sell-with-your-agent) | | **Charge for your API on your own domain (native mode)** | [Native Integration](/products/agentic-commerce/native-integration) | | **See a working paid API end to end** | [l402-example-node](https://github.com/refined-element/l402-example-node) · [l402-example-aspnet](https://github.com/refined-element/l402-example-aspnet) | | **Access L402 APIs from Python/.NET/TypeScript** | [L402 HTTP Clients](/tools/l402-requests) | | **Give Claude ready-made commerce skills** | [lightning-enable-skills](https://github.com/refined-element/lightning-enable-skills) | | **Watch an agent buy its own research data** | [l402-research-agent](https://github.com/refined-element/l402-research-agent) | | **Browse available paid APIs** | [Agent Commerce Store](https://agent-commerce.store) | | **Let agents discover each other on Nostr** | [NostrWolfe](https://nostrwolfe.com) | ## Links | Resource | URL | |----------|-----| | API | [api.lightningenable.com](https://api.lightningenable.com) | | Documentation | [docs.lightningenable.com](https://docs.lightningenable.com) | | Product Page | [lightningenable.com](https://lightningenable.com) | | A-Commerce Manifesto | [a-commerce.lightningenable.com](https://a-commerce.lightningenable.com) | | Parent Company | [refinedelement.com](https://refinedelement.com) | | Support | [support@lightningenable.com](mailto:support@lightningenable.com) | ============================================================================== # First Payment Source: https://docs.lightningenable.com/getting-started/first-payment ============================================================================== # Your First Payment This tutorial walks you through creating, displaying, and confirming your first Lightning Network payment using Lightning Enable. ## What We'll Build A simple payment flow that: 1. Creates a Lightning invoice 2. Displays a QR code for payment 3. Polls for payment confirmation 4. Shows a success message ## Prerequisites - Lightning Enable API key (from your subscription) - Payment provider API key configured in your merchant account (Strike or OpenNode) - Basic HTML/JavaScript knowledge ## Step 1: Create the Payment (server-side) :::danger Never put your API key in browser code `X-API-Key` is your merchant secret — anyone who sees it can create invoices and read your payment data. Always call the Lightning Enable API from **your backend**. (Browser calls would also fail: api.lightningenable.com only allows cross-origin requests from allowlisted origins.) ::: Create the invoice from your server, then hand only the invoice details to the browser: ```javascript // server.js (Node/Express) — your backend, where the API key stays secret app.post('/create-payment', async (req, res) => { const response = await fetch('https://api.lightningenable.com/api/payments', { method: 'POST', headers: { 'X-API-Key': process.env.LIGHTNING_ENABLE_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ orderId: req.body.orderId, amount: 25.00, // price from YOUR catalog — never trust the client currency: 'USD', description: 'Order ' + req.body.orderId, successUrl: 'https://yoursite.com/success' }) }); if (!response.ok) { return res.status(502).json({ error: 'Failed to create payment' }); } const payment = await response.json(); // Send the browser only what it needs to display res.json({ invoiceId: payment.invoiceId, lightningInvoice: payment.lightningInvoice, expiresAt: payment.expiresAt }); }); ``` The response includes everything you need: ```json { "invoiceId": "1042", "status": "unpaid", "lightningInvoice": "lnbc250n1...", "onchainAddress": "bc1q...", "hostedCheckoutUrl": "https://checkout.opennode.com/...", // OpenNode only "expiresAt": "2026-07-03T13:00:00Z" // expiry varies by provider — always read this field } ``` ## Step 2: Display the QR Code Use a QR code library to display the Lightning invoice: ```html Pay with Lightning

Pay $25.00

Scan with your Lightning wallet

Loading...
Waiting for payment...
``` ## Step 3: Poll for Payment Status The browser can poll the **public status endpoint** — it requires no API key and returns only `{ "status": "..." }`, so it is safe to call from client-side code: ```javascript // Browser-safe: no API key needed. Returns { "status": "unpaid" | "processing" | "paid" | "expired" } async function checkPaymentStatus(invoiceId) { const response = await fetch( `https://api.lightningenable.com/api/payments/${invoiceId}/status` ); return await response.json(); } function startPolling(invoiceId) { const statusEl = document.getElementById('status'); const interval = setInterval(async () => { const payment = await checkPaymentStatus(invoiceId); switch (payment.status) { case 'paid': statusEl.className = 'status status-paid'; statusEl.textContent = 'Payment received!'; clearInterval(interval); // Redirect to success page (pass your own order reference) window.location.href = '/success?invoice=' + invoiceId; break; case 'expired': statusEl.className = 'status status-expired'; statusEl.textContent = 'Invoice expired'; clearInterval(interval); break; case 'processing': statusEl.textContent = 'Payment detected, confirming...'; break; } }, 3000); // Check every 3 seconds } // Start polling when page loads startPolling('1042'); ``` :::tip Fulfillment belongs on the server Client-side polling is for UX only. Fulfill orders from your **webhook handler** (see below) or by checking `GET /api/payments/{invoiceId}` from your backend with your API key — never trust the browser to tell you an order was paid. ::: ## Step 4: Handle Success When the payment is confirmed, redirect to a success page: ```html Payment Successful

Payment Successful!

Thank you for your payment.

Order ID:

``` ## Complete Working Example Here's a complete, working HTML file you can use: ```html Lightning Payment Demo

Complete Payment

$25.00 USD
Lightning
Bitcoin
Hosted
Waiting for payment...
``` ## Using Webhooks Instead of Polling For production, webhooks are more reliable than polling: ```javascript // Express.js webhook handler app.post('/webhooks/lightning', express.raw({type: '*/*'}), (req, res) => { const signatureHeader = req.headers['x-lightningenable-signature']; const payload = req.body.toString(); // Parse t={timestamp},v1={hmac} format const parts = Object.fromEntries( signatureHeader.split(',').map(p => p.split('=')) ); const timestamp = parts['t']; const receivedHmac = parts['v1']; // Verify signature: HMAC-SHA256 of "{timestamp}.{payload}" if (!verifySignature(timestamp, payload, receivedHmac, process.env.WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); } const data = JSON.parse(payload); if (data.status === 'paid') { // Process the order processOrder(data.orderId, data.invoiceId); } res.status(200).send('OK'); }); ``` ## Testing with Testnet/Sandbox 1. **Use your provider's test environment:** - Strike sandbox: [dashboard.dev.strike.me](https://dashboard.dev.strike.me) - OpenNode testnet: [dev.opennode.com](https://dev.opennode.com) 2. **Get testnet Bitcoin** from a faucet (OpenNode testnet) or use Strike sandbox 3. **Use a testnet Lightning wallet** (Polar, Thunderhub) 4. **Pay the invoice** and watch the status change ## Next Steps - [Webhooks Implementation](/api-reference/webhooks) - Instant payment notifications - [Error Handling](/api-reference/errors) - Handle edge cases ============================================================================== # Your First Settlement Source: https://docs.lightningenable.com/getting-started/first-settlement ============================================================================== # Your First Settlement In Lightning Enable, settlements and payments follow the same flow. A "settlement" is simply a completed payment that has been settled to your payment provider account. For the complete guide on creating and processing payments (which result in settlements), see **[Your First Payment](./first-payment.md)**. ## Settlement vs Payment - **Payment**: A Lightning invoice has been created and is awaiting payment - **Settlement**: The payment has been confirmed and funds are settled to your provider account Your payment provider (Strike or OpenNode) handles the settlement process automatically. Check your provider dashboard for settlement details and timing. ============================================================================== # Introduction Source: https://docs.lightningenable.com/ ============================================================================== > **Lightning Enable — infrastructure for agent commerce over Lightning.** *See the [full ecosystem map](/getting-started/ecosystem) for every component, package, and live demo.* # Introduction to Lightning Enable **Lightning Enable is infrastructure for agent commerce over Lightning.** It lets autonomous agents discover, buy, and get paid for digital work — over the Lightning Network, within the budgets and rules their humans control. AI agents are starting to buy. They call APIs, run workflows, and pay for tools. But card rails assume a human identity, a checkout flow, and a billing address — none of which an agent workflow has. Lightning per-request payments fit: an agent hits a paid endpoint, gets an HTTP `402` with a Lightning invoice, pays it in about a second, and retries with cryptographic proof. It gets in; you get paid. Lightning Enable is the software that makes an agent and an API speak that language — with no Lightning node, no hosted middleman, and no per-transaction cut. :::info Lightning Enable never holds your funds Lightning Enable is API middleware. **Lightning Enable does not hold funds — your payment provider (Strike or OpenNode) facilitates custody and settlement.** Every payment your API earns settles directly into *your own* provider account. Lightning Enable charges a flat monthly subscription, never a percentage of your revenue. ::: ## Two sides of the same rail Agent commerce has a buyer and a seller. Lightning Enable serves both on one rail — pick the side you're on: ``` BUY side (agents / consumers) SELL side (producers / API builders) ┌───────────────────────────────────┐ ┌───────────────────────────────────────┐ │ Free, open-source MCP server │ │ Create L402 producer endpoints │ │ Bring your own wallet │ │ Return HTTP 402 + Lightning invoice │ │ (Strike · NWC · LND) │ │ Verify the payment proof │ │ Pay & access any L402 API │ │ Get paid per request, in sats │ └────────────────┬──────────────────┘ └────────────────────┬──────────────────┘ │ same L402 rail, same settlement │ └────────────────────┬───────────────────────┘ ▼ Strike / OpenNode (custody + settlement) ``` ### For agents (the buy side) Agents pay with the **free, open-source [MCP server](/products/agentic-commerce/mcp-quickstart)** (MIT — a compact action-based tool set, with a minimal lite profile for pay-only agents; most tools work out of the box with no API key — see the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the full breakdown). Connect your own wallet — Strike, an NWC wallet, or LND — and your agent can pay Lightning invoices, access L402-protected APIs, and enforce spending budgets. No hosted MCP and no Lightning node required. Using the MCP as a buyer is **always free**. ### For producers (the sell side) Producers turn any HTTP API into a paid one. Gate a route, return a `402` challenge, and get paid per request in real Bitcoin — while your existing subscriptions, rate limits, and API keys stay exactly as they are. Lightning per-request runs *alongside* what you already charge; it's an additional revenue stream, not a replacement. This is the part that needs a Lightning Enable plan (or the free sandbox below). ## Fastest path to first success Pick your side and get a real payment moving in minutes. **If you're building an agent:** install the MCP, connect a wallet, and run the built-in self-test. ```bash # .NET tool (recommended) dotnet tool install --global LightningEnable.Mcp # or Python pip install lightning-enable-mcp ``` Then have your agent call **`test_l402_payment`** — it pays a tiny live L402 challenge end-to-end and confirms your wallet is wired up correctly. Full walkthrough: [MCP Quick Start](/products/agentic-commerce/mcp-quickstart) · tool reference: [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide). **If you're monetizing an API:** start the **Free Producer Sandbox** (no card), create your first L402 endpoint, and watch an agent pay it. - Start free: [api.lightningenable.com/dashboard/signup](https://api.lightningenable.com/dashboard/signup) - Then follow [Monetize Your API in 10 Minutes](/products/agentic-commerce/api-monetization) to gate a route and take your first payment. ## L402 in one breath L402 is the protocol underneath both sides. Payment *becomes* authorization: ``` 1. Agent requests a resource → GET /api/premium/data 2. Server answers 402 + invoice → HTTP 402 Payment Required WWW-Authenticate: L402 macaroon="…", invoice="lnbc…" 3. Agent pays the Lightning invoice → (~1 second, obtains the preimage) 4. Agent retries with the token → Authorization: L402 : 5. Server verifies & serves → HTTP 200 OK ``` The proof is cryptographic: the server checks that `SHA256(preimage) == payment_hash`. No accounts, no sign-up, no card on file — just a tiny payment and a receipt. Deeper dive: [How It Works](/products/agentic-commerce/how-it-works) and the [L402 Producer API](/products/agentic-commerce/l402-producer-api). ## Three ways to start You don't have to reach for a credit card to begin. There are three on-ramps — pick the one that fits: | On-ramp | Cost | What you get | Start | |---------|------|--------------|-------| | **Free Producer Sandbox** | Free — no card | 3 endpoints · 200 challenges/mo · 1,000 sats max per challenge | Email + magic link at [dashboard/signup](https://api.lightningenable.com/dashboard/signup) | | **L402 Fast Lane** | 100 sats — no card, no form | A full **30-day Agentic Commerce** trial | Pay the challenge — [Activate with Lightning](/getting-started/activate-with-lightning) | | **Stripe Trial** | Card — no charge for 30 days | A full **30-day Agentic Commerce** trial | [Start the card trial](https://api.lightningenable.com/Checkout?plan=individual) | **L402 Fast Lane** is "the signup form that IS the protocol": you `POST` to the signup endpoint, get a `402`, pay a 100-sat Lightning challenge, and retry with the macaroon proof to activate the account — the same L402 flow your own users and agents will use. See [Activate with Lightning](/getting-started/activate-with-lightning) for the MCP, dev-CLI, and raw-protocol paths. ## Plans Every plan is a flat monthly subscription — **no per-transaction fees from us**. Free is the risk-free way to start; Agentic Commerce is the self-serve paid plan; teams that want white-glove onboarding and direct founder access can contact us about Agentic Commerce — Business. | Plan | Price | Highlights | |------|-------|------------| | **Free Producer Sandbox** | $0 | Prove L402 works with no card: 3 endpoints, 200 challenges/mo, 1,000 sats max per challenge | | **Agentic Commerce** | $49/mo | Unlimited L402 endpoints, Strike settlement, per-endpoint pricing, live dashboard, 30-day trial | | **Agentic Commerce — Business** | [Contact us](mailto:support@lightningenable.com) | Everything in Agentic Commerce **+** white-glove onboarding and direct founder access | See the [full plan comparison](/products/product-overview) for the feature-by-feature breakdown, and the [Agentic Commerce overview](/products/agentic-commerce/overview) for the product itself. :::note Using the MCP is free Only *creating* L402-protected endpoints needs a plan (or the Free Producer Sandbox). **Using** the MCP server as an agent to pay and access L402 APIs is free and needs no subscription. ::: ## Platform integrations (included with any plan) Already run a storefront? Lightning Enable drops L402 checkout into commerce platforms so agents (and bitcoin-aligned customers) can buy in sats: - **[Shopify Commerce](/products/shopify-commerce/overview)** — L402 agentic commerce for Shopify stores. ## Where to go next - **Building an agent?** [MCP Quick Start](/products/agentic-commerce/mcp-quickstart) → [Wallet Setup](/products/agentic-commerce/mcp-wallet-setup) → [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) - **Monetizing an API?** [Monetize Your API in 10 Minutes](/products/agentic-commerce/api-monetization) → [L402 Producer API](/products/agentic-commerce/l402-producer-api) → [Dashboard Guide](/products/agentic-commerce/dashboard-guide) - **Just exploring?** [How It Works](/products/agentic-commerce/how-it-works) · [Ecosystem Map](/getting-started/ecosystem) · [Prerequisites](/getting-started/prerequisites) - **Setting up a provider?** [Strike (recommended)](/strike-setup/account-setup) or [OpenNode](/opennode-setup/account-setup) - **Accepting Bitcoin at a checkout instead of gating an API?** The e-commerce [Quick Start](/getting-started/quick-start) covers the invoice → QR → confirmation flow. ## Getting help - **Documentation:** you're reading it. - **Email:** support@lightningenable.com - **Strike support:** [strike.me](https://strike.me) - **OpenNode support:** [developers.opennode.com](https://developers.opennode.com) --- *Lightning Enable is a product of [Refined Element, LLC](https://refinedelement.com). It is API middleware and never holds your funds — your chosen settlement provider (Strike or OpenNode) facilitates custody and settlement, and every payment settles directly into your own provider account.* ============================================================================== # Prerequisites Source: https://docs.lightningenable.com/getting-started/prerequisites ============================================================================== # Prerequisites Before you begin integrating Lightning Enable, ensure you have the following ready. :::caution Provider Verification Required You **must** complete verification with your chosen payment provider (Strike or OpenNode) before you can accept payments. This is a regulatory requirement and cannot be bypassed. ::: ## Required ### 1. Payment Provider Account You need an account with at least one payment provider. **Strike is the default and recommended for most merchants.** #### Option A: Strike (Recommended) | Environment | URL | Purpose | |-------------|-----|---------| | **Sandbox** (Development) | [dashboard.dev.strike.me](https://dashboard.dev.strike.me) | Testing | | **Production** | [dashboard.strike.me](https://dashboard.strike.me) | Real payments | **Steps:** 1. Sign up at [strike.me](https://strike.me) 2. Complete KYC verification 3. Generate an API key from your dashboard 4. Store the API key securely [Detailed Strike Setup Guide](/strike-setup/account-setup) #### Option B: OpenNode | Environment | URL | Purpose | |-------------|-----|---------| | **Testnet** (Development) | [dev.opennode.com](https://dev.opennode.com) | Testing with fake Bitcoin | | **Mainnet** (Production) | [app.opennode.com](https://app.opennode.com) | Real payments | **Steps:** 1. Sign up at OpenNode 2. **Complete KYB verification** (required for mainnet, plan 2-4 business days) 3. Generate an API key 4. Store the API key securely [Detailed OpenNode Setup Guide](/opennode-setup/account-setup) ### 2. Lightning Enable Account You need a Lightning Enable account for your **merchant API key** — the key that lets you *create* L402-protected endpoints. Both paid plans include the full L402 stack (unlimited endpoints, per-endpoint pricing, the Producer API); the difference is settlement-provider choice and support, not whether you can monetize APIs. **Available Plans:** | Plan | Monthly | Best For | |------|---------|----------| | Free Producer Sandbox | Free — no card | Hobbyists, demos, and side projects (3 endpoints, 200 challenges/mo, 1,000 sats max per challenge) | | Agentic Commerce | $49 | Individual developers and side projects (full L402, Strike settlement) | | Agentic Commerce — Business | Contact us | Teams and platforms — white-glove onboarding and direct founder access | Agentic Commerce and Agentic Commerce — Business both include access to all platform integrations (Shopify Commerce). Agentic Commerce also includes a **30-day free trial** through self-serve checkout; Business is contact-only (email support@lightningenable.com — it isn't purchasable through `/Checkout`), and any trial terms are arranged directly. :::tip You don't need a card to start Get a merchant API key for free with the **Free Producer Sandbox** ([sign up](https://api.lightningenable.com/dashboard/signup) — 3 endpoints, no card), or unlock a full 30-day Individual trial by paying a 100-sat Lightning challenge — no card, no form — via [**L402 Fast Lane**](/getting-started/activate-with-lightning). ::: After subscribing, you'll receive: - Your Lightning Enable API key - Access to the merchant dashboard - Webhook configuration options ### 3. Development Environment Any platform that can make HTTP REST API calls: **Recommended:** - .NET 9 / ASP.NET Core - Node.js / TypeScript - Python - Any language with HTTP client support **Technical Requirements:** - HTTPS support (required for webhooks) - Ability to store API keys securely - JSON parsing capability ## Recommended ### Webhook Endpoint For production use, webhooks provide instant payment notifications: - **Public HTTPS URL** (not localhost) - **Ability to validate HMAC signatures** - **Idempotent processing** (webhooks may retry) For local development, use [ngrok](https://ngrok.com) to expose your local server. ### Database To track payments and orders: - SQL Server (recommended for .NET) - PostgreSQL - MySQL - Any relational database ### SSL/TLS Certificate Required for: - Webhook verification - API security - Production deployment ## Platform-Specific Requirements ### For Agentic Commerce If you're implementing L402 API monetization: - **Production provider key** (mainnet/production payments required) - **Public API endpoint** to protect - **Understanding of macaroons** (bearer tokens) - **Strike recommended** for L402 (returns preimage directly, more reliable than BOLT11 parsing) ## Environment Checklist Use this checklist before starting integration: ### Development - [ ] Payment provider account created (Strike or OpenNode) - [ ] Test/sandbox API key generated - [ ] Lightning Enable subscription active - [ ] Merchant API key received - [ ] Development environment ready - [ ] ngrok installed (for webhook testing) ### Production - [ ] Payment provider production account created - [ ] **Provider verification completed** (Strike KYC or OpenNode KYB) - [ ] Production API key generated - [ ] Production Lightning Enable API key configured - [ ] Webhook endpoint deployed and accessible - [ ] HTTPS configured - [ ] API keys stored securely (not in code) - [ ] Error logging and monitoring enabled ## Next Steps Once you have all prerequisites: 1. [Quick Start Guide](/getting-started/quick-start) - Create your first payment 2. Provider Setup — [Strike (recommended)](/strike-setup/account-setup) or [OpenNode](/opennode-setup/account-setup) 3. Choose your product: - [Agentic Commerce](/products/agentic-commerce/overview) - [Shopify Commerce](/products/shopify-commerce/overview) ============================================================================== # Quick Start Source: https://docs.lightningenable.com/getting-started/quick-start ============================================================================== # Quick Start Guide Get up and running with Lightning Enable in under 10 minutes. This guide walks you through creating your first Lightning Network payment for an **e-commerce / checkout** integration (invoice → QR → confirmation flow). :::info Building an L402-paid API instead? If your goal is to **monetize an API for AI agents** (pay-per-request gating, not checkout) you want the L402 walkthrough, not this page: [**Monetize Your API in 10 Minutes**](/products/agentic-commerce/api-monetization). The two flows use different endpoints and a different mental model. This Quick Start covers the older e-commerce / Bitcoin-checkout path; L402 is the API-monetization path. ::: :::tip Don't have an API key yet? [Subscribe here](https://api.lightningenable.com) to get your API key and start accepting Lightning payments. ::: :::caution Provider Verification Required Before accepting real payments, you must complete verification with your payment provider (Strike or OpenNode). ::: ## Prerequisites Before you begin, ensure you have: 1. **Payment Provider Account** with API key - **Strike** (recommended): [dashboard.strike.me](https://dashboard.strike.me) (sandbox: [dashboard.dev.strike.me](https://dashboard.dev.strike.me)) - **OpenNode**: [app.opennode.com](https://app.opennode.com) (testnet: [dev.opennode.com](https://dev.opennode.com)) 2. **Lightning Enable Subscription** with your API key 3. **HTTP Client** - curl, Postman, or any programming language ## Step 1: Test Authentication Verify the API is running by calling the health endpoint (no authentication required): ```bash curl https://api.lightningenable.com/health ``` **Expected Response:** ```json { "status": "Healthy", "totalDuration": 42.15, "checks": [ { "name": "database", "status": "Healthy", "duration": 38.72, "description": null, "exception": null, "tags": ["db", "sql"] } ] } ``` Then verify your API key works by calling an authenticated endpoint: ```bash curl -X GET https://api.lightningenable.com/api/merchant/me \ -H "X-API-Key: your-api-key-here" ``` ## Step 2: Configure Your Payment Provider Your Lightning Enable account needs your provider API key to create payments. This is configured during signup or via the merchant settings API: ```bash # Configure Strike (recommended) — saving the key also defaults you to the Strike provider curl -X PUT https://api.lightningenable.com/api/merchant/strike-key \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{"strikeApiKey": "your-strike-api-key"}' # Or configure OpenNode curl -X PUT https://api.lightningenable.com/api/merchant/opennode-key \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{"openNodeApiKey": "your-opennode-api-key"}' # Set the active provider explicitly (provider is a string: "strike" or "opennode") curl -X PUT https://api.lightningenable.com/api/merchant/payment-provider \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{"provider": "strike"}' # Optional: register a callback URL for webhook notifications curl -X PUT https://api.lightningenable.com/api/merchant/webhook-url \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{"webhookUrl": "https://yourapp.com/webhooks/lightning", "webhookSecret": "your-signing-secret"}' ``` ## Step 3: Create Your First Payment Create a Lightning invoice for a customer purchase: ```bash curl -X POST https://api.lightningenable.com/api/payments \ -H "X-API-Key: your-api-key-here" \ -H "Content-Type: application/json" \ -d '{ "orderId": "ORDER-12345", "amount": 25.00, "currency": "USD", "description": "Premium Widget", "customerEmail": "customer@example.com", "successUrl": "https://yourapp.com/order/success" }' ``` **Response:** ```json { "invoiceId": "1042", "providerChargeId": "charge_xyz789", "status": "unpaid", "amount": 25.00, "currency": "USD", "lightningInvoice": "lnbc250000n1p...", "onchainAddress": "bc1q...", "hostedCheckoutUrl": "https://checkout.opennode.com/charge_xyz789", // OpenNode only "createdAt": "2026-07-03T12:00:00Z", "expiresAt": "2026-07-03T13:00:00Z" } ``` :::note Invoice expiry varies by provider Always read the `expiresAt` field rather than assuming a fixed window. Strike invoices (the recommended default) get ~60 minutes; OpenNode uses the provider's TTL; demo invoices (`amount: 0`) expire in 10 minutes. ::: ## Step 4: Display Payment Options You have three options for accepting payment: ### Option A: Hosted Checkout (OpenNode Only) Redirect the customer to the hosted checkout page (only available when using OpenNode as your payment provider): ```javascript // JavaScript window.location.href = response.hostedCheckoutUrl; ``` ```csharp // C# / ASP.NET Core return Redirect(response.HostedCheckoutUrl); ``` ### Option B: Lightning Invoice (QR Code) Display the Lightning invoice as a QR code: ```html
``` ### Option C: On-Chain Bitcoin Address Display the Bitcoin address for on-chain payment (slower, higher fees): ```html

Send Bitcoin to: bc1q...

``` ## Step 5: Check Payment Status Poll the payment status endpoint: ```bash curl -X GET https://api.lightningenable.com/api/payments/1042 \ -H "X-API-Key: your-api-key-here" ``` **Response (Unpaid):** ```json { "invoiceId": "1042", "orderId": "ORDER-12345", "status": "unpaid", "amount": 25.00, "currency": "USD", "paidAt": null } ``` **Response (Paid):** ```json { "invoiceId": "1042", "orderId": "ORDER-12345", "status": "paid", "amount": 25.00, "currency": "USD", "paidAt": "2026-07-03T12:03:45Z" } ``` ## Step 6: Handle Webhooks (Recommended) Instead of polling, configure webhooks for instant payment notifications: **Webhook Payload:** ```json { "invoiceId": "1042", "orderId": "ORDER-12345", "status": "paid", "amount": 25.00, "currency": "USD", "paidAt": "2026-07-03T12:03:45Z" } ``` **Verify Webhook Signature:** The `X-LightningEnable-Signature` header has the format `t={unix_timestamp},v1={hmac_sha256_hex}`. Compute HMAC-SHA256 of `{timestamp}.{payload}` using your webhook secret. ```csharp using System.Security.Cryptography; using System.Text; public bool VerifyWebhookSignature(HttpRequest request, string payload, string secret) { var header = request.Headers["X-LightningEnable-Signature"].ToString(); // Parse t={timestamp},v1={hmac} format var parts = header.Split(',') .Select(p => p.Split('=', 2)) .ToDictionary(p => p[0], p => p[1]); var timestamp = parts["t"]; var receivedHmac = parts["v1"]; // Sign "{timestamp}.{payload}" var signingInput = $"{timestamp}.{payload}"; using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(signingInput)); var computedHmac = Convert.ToHexString(hash).ToLowerInvariant(); // Constant-time comparison to avoid leaking the signature under timing analysis return CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(computedHmac), Encoding.UTF8.GetBytes(receivedHmac)); } ``` ## Payment Status Flow ``` +---------+ Customer pays +------------+ Confirmed +------+ | unpaid |-------------------->| processing |---------------->| paid | +---------+ +------------+ +------+ | | expiry (provider-dependent — see expiresAt) v +---------+ | expired | +---------+ ``` **Status Codes:** | Status | Description | |--------|-------------| | `unpaid` | Invoice created, awaiting payment | | `processing` | Payment detected, confirming | | `paid` | Payment confirmed, fulfill order | | `expired` | Invoice expired without payment | | `underpaid` | Insufficient amount received | ## Complete Example: Node.js ```javascript const axios = require('axios'); const API_URL = 'https://api.lightningenable.com'; const API_KEY = 'your-api-key-here'; async function createPayment() { // Create payment const response = await axios.post( `${API_URL}/api/payments`, { orderId: `ORDER-${Date.now()}`, amount: 25.00, currency: 'USD', description: 'Premium Widget', successUrl: 'https://yourapp.com/order/success' }, { headers: { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' } } ); console.log('Lightning Invoice:', response.data.lightningInvoice); // hostedCheckoutUrl is only present when using OpenNode as provider if (response.data.hostedCheckoutUrl) { console.log('Hosted Checkout URL:', response.data.hostedCheckoutUrl); } return response.data; } createPayment(); ``` ## Complete Example: C# ```csharp using System.Net.Http; using System.Net.Http.Json; public class LightningEnableClient { private readonly HttpClient _httpClient; private const string ApiUrl = "https://api.lightningenable.com"; public LightningEnableClient(string apiKey) { _httpClient = new HttpClient(); _httpClient.DefaultRequestHeaders.Add("X-API-Key", apiKey); } public async Task CreatePaymentAsync( string orderId, decimal amount, string description) { var request = new { orderId, amount, currency = "USD", description }; var response = await _httpClient.PostAsJsonAsync( $"{ApiUrl}/api/payments", request ); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); } } ``` ## Troubleshooting **Problem:** `401 Unauthorized` response **Solution:** Check that your API key is correct and included in the `X-API-Key` header. --- **Problem:** `400 Bad Request - Merchant API key not configured` **Solution:** Configure your payment provider API key (Strike or OpenNode) in Lightning Enable merchant settings. --- **Problem:** Payment stuck in `unpaid` status **Solution:** - Check the invoice hasn't expired — read the `expiresAt` field (expiry is provider-dependent; see the note in Step 3) - Verify the Lightning invoice or Bitcoin address - Test with your provider's sandbox/testnet environment first ## Next Steps - [First Payment Tutorial](/getting-started/first-payment) - Detailed walkthrough - [Webhooks Setup](/api-reference/webhooks) - Stop polling, use webhooks - Provider Configuration — [Strike (recommended)](/strike-setup/account-setup) or [OpenNode](/opennode-setup/account-setup) ============================================================================== # Sell With Your Agent Source: https://docs.lightningenable.com/getting-started/sell-with-your-agent ============================================================================== # Sell With Your Agent: Zero to a Paid API You already activated a Lightning Enable account (the [previous step](./activate-with-lightning)). This guide is the next one: turn that account into a **paid, agent-discoverable endpoint** without touching the dashboard or writing any integration code yourself. The [`producer-setup` skill](https://github.com/refined-element/lightning-enable-skills/tree/main/skills/producer-setup) drives the whole thing through the Lightning Enable MCP server. You give your agent three things and one sentence; it does the rest — configuring your receive lane, wrapping your API, publishing a machine-readable manifest, and proving the whole path works with a real, tiny Lightning payment before it tells you it's done. :::tip Works wherever the MCP server runs Claude Code, Claude Desktop, or any MCP-compatible agent host with the [Lightning Enable MCP](/products/agentic-commerce/mcp-quickstart) connected and a merchant API key set (`LIGHTNING_ENABLE_API_KEY`). If you don't have a key yet, [activate one for free with a ~100-sat L402 payment](./activate-with-lightning) — no card. `setup_wallet` and `l402_producer` ship in the default `standard` [tool profile](/products/agentic-commerce/mcp-complete-guide#tool-profiles) — nothing extra to enable. ::: ## What you provide Three things, and the agent needs no more from you than this: | You provide | Why | |---|---| | **A wallet to receive payments** — a Nostr Wallet Connect (NWC) connection string from a wallet you already run (CoinOS, Alby Hub, or another NIP-04 wallet), *or* a Strike/OpenNode API key | This is where the sats your endpoint earns land. Lightning Enable never touches it beyond asking for an invoice and asking later whether it was paid. | | **A spend ceiling for the agent's own wallet** — a dollar or sat cap, e.g. "$5 max" | The agent pays a tiny self-test challenge against your new endpoint before handing it back to you, so it needs a small amount of its own spending room. This is the same budget mechanism documented in [AI Spending Security](/products/agentic-commerce/ai-spending-security) — the wallet balance and the config-file caps are the real ceiling, not the agent's judgment. | | **The API to monetize, and a price** — a base URL and a sats-per-call figure | What gets wrapped and what it costs. Any HTTPS API works; see [Setting Up Your Proxy](/products/agentic-commerce/proxy-setup-walkthrough) for the constraints (no raw IPs, no internal hostnames, standard ports only). | ## The one sentence This is the literal instruction — say it to your agent once the MCP server is connected: > Set me up to sell my API over Lightning: use the wallet you already have connected to receive payments, keep the signup and self-test under 200 sats, charge 25 sats per call on `https://api.mycompany.com/v1/forecast`, publish it, and show me the OpenAPI URL and the challenge feed when you are done. Swap in your own connection string, URL, and price. If you're paying with Strike or OpenNode instead of NWC, say "using my Strike API key" — the skill accepts either. ## What the skill does, step by step The skill calls the Lightning Enable MCP tools in this order. Nothing here requires you to open the dashboard. 1. **`setup_wallet`** — if the agent doesn't already have a paying wallet configured, this writes `~/.lightning-enable/config.json` with a small wallet and the spend ceiling you gave it. This is the wallet the agent uses in step 7 to pay its own self-test challenge — it is separate from the receive-side wallet in the next step. 2. **`l402_producer(action="configure_receive", ...)`** — saves your receive-side credential and switches your account onto that lane. For NWC this wraps `PUT /api/merchant/nwc-connection` followed by `PUT /api/merchant/payment-provider`; for Strike/OpenNode it wraps the equivalent key endpoint. See [Nostr Wallet Connect setup](../nwc-setup/account-setup.md) for what this credential looks like and what it can and can't do. 3. **`l402_producer(action="status")`** — confirms the receive lane actually took (provider set, credential present, reachable) before anything downstream depends on it. Cheap, read-only, and worth insisting the agent check before it moves on. 4. **`l402_producer(action="create_proxy", ...)`** — wraps your API in an L402-gated proxy pointed at the target URL and default price (`POST /api/proxy`). 5. **`l402_producer(action="add_endpoint", ...)`** — registers the specific path, method, summary, and price you asked for so it's visible in the manifest with its own description, not just the proxy's default fallback price. 6. **`l402_producer(action="publish", ...)`** — turns on the manifest and (if you asked for discoverability) lists it in the public L402 registry. 7. **`l402_producer(action="create", ...)`** then a payment from the wallet configured in step 1 — the skill mints a one-off, tiny challenge against your brand-new endpoint and pays it itself, inside the spend ceiling you set. 8. **`l402_producer(action="verify", ...)`** — verifies its own payment. This is the same cryptographic check any real buyer's payment goes through: `SHA256(preimage) == payment_hash`, macaroon signature valid, not expired. A `valid: true` here means a stranger paying the same way would also get in. 9. **`l402_producer(action="list_challenges")`** — pulls the challenge feed back so you can see the self-test entry as a receipt, not just take the agent's word for it. If any step fails, the skill stops and reports which one — it does not silently retry into a different configuration than the one you asked for. ## What you get back At the end of a successful run, the agent hands you four things: - **An OpenAPI document** at `/l402/proxy/{proxyId}/openapi.json` — every visible endpoint carries an `x-payment` extension (price in sats, supported payment protocols, token validity window) and a `402` response pointing at the L402 challenge shape. Point any OpenAPI-aware tool at this URL directly. - **A manifest** at `/l402/proxy/{proxyId}/.well-known/l402-manifest.json` — the L402-native discovery document other agents read to learn what your API does and what it costs. Both documents describe the same endpoints from the same pricing logic; they can't disagree. - **A challenge listing** — `GET /api/l402/challenges?status=paid` shows every payment your endpoint has collected, newest first, with `paymentHash`, `resource`, `amountSats`, and `paidAt`. This is your feed, not a settlement record — see the next section. - **A `l402.challenge.paid` webhook**, once you point `CallbackUrl` at your own server (the skill can set this too, if you gave it a URL — otherwise set it yourself at **Dashboard → Settings → Webhooks** or via `PUT /api/merchant/webhook-url`). Fires the first time each challenge is proven paid, HMAC-signed the same way as every other Lightning Enable webhook. ## Custody and fees Lightning Enable does not hold funds. Every sat your endpoint earns settles directly with your wallet — your own NWC wallet facilitates custody and settlement on that lane, or your chosen payment provider (Strike or OpenNode) does if you configured one of those instead. Lightning Enable never sits in the money's path; it mints the invoice, verifies the proof of payment, and gets out of the way. There is no per-transaction fee from Lightning Enable on any plan. Your wallet or payment provider may apply its own routing or processing fee — check their fee schedule, not ours. Selling requires a producer account: the [Fast Lane trial](./activate-with-lightning) or the Free Producer Sandbox described below. ## Trial, then the Free Producer Sandbox If you activated with the Fast Lane, you're on a 30-day **Agentic Commerce** trial: unlimited endpoints, no volume cap on challenges, no per-challenge price ceiling. Everything above works at any scale during the trial. When the trial ends without billing added, your account automatically moves to the **Free Producer Sandbox** — you keep the account and every endpoint you built, but three caps now apply: **3 endpoints** (distinct resource paths, counted for the life of the account, never resets), **200 challenges/month**, and **1,000 sats max per challenge**. Add billing (Agentic Commerce, $49/month) at any point to remove the caps without losing anything you've built. Nothing above changes if you start directly on the Sandbox instead of the trial — the same skill and the same tool calls work; `add_endpoint` and `create` simply start refusing once you hit a cap, with a `402` naming which one. ## Troubleshooting **"The wallet did not answer within 30s."** This is an NWC timeout, and it can surface at two different points: during `configure_receive` (your receive wallet didn't respond to the connection check) or during the agent's own self-test payment in step 7 (its paying wallet, if also NWC, didn't respond). Check the connection is still listed in the wallet app and that it grants `make_invoice` and `lookup_invoice`; see [Nostr Wallet Connect setup — Troubleshooting](../nwc-setup/account-setup.md#troubleshooting) for the full list of NWC failure modes. **The self-test payment fails when the receiving and paying wallets are the same node.** If you pointed both `configure_receive` and the agent's own wallet (`setup_wallet`) at the *same* underlying Lightning node or NWC wallet, the self-test payment in step 7 can fail — most Lightning implementations refuse to route a payment back to the node that issued the invoice, since there's no real path to route through. This isn't a Lightning Enable restriction; it's how Lightning payment routing works. Use two different wallets — the receive side can stay whatever you chose, but give the agent a second, separate small wallet to pay from. This is exactly the setup already recommended in [Run L402 Anywhere: Hermes + NWC](/products/agentic-commerce/hermes-nwc-setup) and [First success after activation](./activate-with-lightning#first-success-after-activation) — a dedicated small paying wallet, kept separate from anything that receives. **500 "Payment configuration error" on the first 402.** This is different from the clean `400 payment_provider_not_configured` refusal (see the [Producer API Reference](/products/agentic-commerce/producer-api-reference#error-format)), which means no credential at all is on file. A `500` with "Payment configuration error" means a credential **is** on file but isn't usable — confirmed causes: a placeholder/invalid API key saved for Strike or OpenNode, or an NWC wallet that never answers the invoice request (`NWC_NO_RESPONSE`, including a wallet that's offline or requires an encryption scheme Lightning Enable doesn't speak — see [NIP-04 requirement](../nwc-setup/account-setup.md#choose-a-wallet)). Fix it with `configure_receive` (re-save a real credential) and, if the agent's own wallet is the one that's unusable, `setup_wallet` — then retry. Run `l402_producer(action="status")` (or `GET /api/merchant/quickstart` by hand) afterward to confirm the lane is actually live before minting again. ## Do it by hand (raw REST) Everything above is the same handful of HTTP calls the skill makes for you. Useful if you're integrating from a language without the skill, or want to see exactly what's happening. All calls need `X-API-Key: $LIGHTNING_ENABLE_API_KEY`. **1. Configure the receive lane (NWC shown; swap for `strike-key`/`opennode-key` if using a provider):** ```bash curl -X PUT https://api.lightningenable.com/api/merchant/nwc-connection \ -H "X-API-Key: $LIGHTNING_ENABLE_API_KEY" -H "Content-Type: application/json" \ -d '{"nwcConnectionString": "nostr+walletconnect://?relay=wss://&secret="}' curl -X PUT https://api.lightningenable.com/api/merchant/payment-provider \ -H "X-API-Key: $LIGHTNING_ENABLE_API_KEY" -H "Content-Type: application/json" \ -d '{"provider": "nwc"}' ``` **2. Check status:** ```bash curl https://api.lightningenable.com/api/merchant/quickstart \ -H "X-API-Key: $LIGHTNING_ENABLE_API_KEY" ``` **3. Create the proxy:** ```bash curl -X POST https://api.lightningenable.com/api/proxy \ -H "X-API-Key: $LIGHTNING_ENABLE_API_KEY" -H "Content-Type: application/json" \ -d '{ "name": "Forecast API", "targetBaseUrl": "https://api.mycompany.com", "defaultPriceSats": 25, "description": "7-day forecast, priced per call" }' ``` Note the `proxyId` in the response (e.g. `forecast-api-a1b2`) — every call below uses it. **4. Add the endpoint to the manifest:** ```bash curl -X POST https://api.lightningenable.com/api/proxy/forecast-api-a1b2/manifest/endpoints \ -H "X-API-Key: $LIGHTNING_ENABLE_API_KEY" -H "Content-Type: application/json" \ -d '{ "endpointId": "forecast", "path": "/v1/forecast", "httpMethod": "GET", "summary": "7-day forecast", "basePriceSats": 25 }' ``` Keep this price in agreement with the proxy's `defaultPriceSats` (or any per-path pricing rule) — a mismatch shows up as a "Sync" warning in the dashboard; see [Step 5 of the proxy walkthrough](/products/agentic-commerce/proxy-setup-walkthrough#step-5--review-pricing-per-endpoint) if you ever set them differently on purpose. **5. Publish (enable the manifest, optionally list in the registry):** ```bash curl -X PUT https://api.lightningenable.com/api/proxy/forecast-api-a1b2/manifest/settings \ -H "X-API-Key: $LIGHTNING_ENABLE_API_KEY" -H "Content-Type: application/json" \ -d '{ "manifestEnabled": true, "serviceDescription": "7-day weather forecasts, priced per call.", "manifestPubliclyListed": true }' ``` **6. (Optional) Point the payment webhook at your own server:** ```bash curl -X PUT https://api.lightningenable.com/api/merchant/webhook-url \ -H "X-API-Key: $LIGHTNING_ENABLE_API_KEY" -H "Content-Type: application/json" \ -d '{"webhookUrl": "https://your-server.example.com/webhooks/lightning-enable"}' ``` **7–8. Mint and pay a self-test challenge** (or ask a real caller to — the endpoint is live once step 5 completes): ```bash curl -X POST https://api.lightningenable.com/api/l402/challenges \ -H "X-API-Key: $LIGHTNING_ENABLE_API_KEY" -H "Content-Type: application/json" \ -H "Idempotency-Key: self-test-1" \ -d '{"resource": "/v1/forecast", "priceSats": 25, "description": "Self-test"}' ``` Pay the returned invoice with any Lightning wallet that surfaces a preimage. **9. Verify:** ```bash curl -X POST https://api.lightningenable.com/api/l402/challenges/verify \ -H "X-API-Key: $LIGHTNING_ENABLE_API_KEY" -H "Content-Type: application/json" \ -d '{"macaroon": "", "preimage": "", "resource": "/v1/forecast"}' ``` **10. List challenges:** ```bash curl "https://api.lightningenable.com/api/l402/challenges?status=paid&limit=10" \ -H "X-API-Key: $LIGHTNING_ENABLE_API_KEY" ``` Full contract for every call above — request/response shapes, error codes, idempotency rules, caveat enforcement — is in the [Producer API Reference](/products/agentic-commerce/producer-api-reference) and [Proxy Configuration](/products/agentic-commerce/proxy-configuration). ## Next steps - [L402 Producer API: Agents That Earn](/products/agentic-commerce/l402-producer-api) — the full producer guide, MCP tool parameters, and idempotency rules - [Producer API Reference](/products/agentic-commerce/producer-api-reference) — complete REST contract for every call above - [Setting Up Your Proxy](/products/agentic-commerce/proxy-setup-walkthrough) — the manual dashboard walkthrough this guide automates, including the OpenAPI document details - [Nostr Wallet Connect setup](../nwc-setup/account-setup.md) — the receive lane in depth - [AI Spending Security](/products/agentic-commerce/ai-spending-security) — how the agent's own spend ceiling is enforced --- *Lightning Enable is API middleware and never holds your funds. Your wallet, or your chosen payment provider (Strike or OpenNode), facilitates custody and settlement of every payment your endpoint earns.* ============================================================================== # Nostr Wallet Connect Setup Source: https://docs.lightningenable.com/nwc-setup/account-setup ============================================================================== # Nostr Wallet Connect setup Nostr Wallet Connect (NWC, [NIP-47](https://github.com/nostr-protocol/nips/blob/master/47.md)) connects Lightning Enable to a Lightning wallet you already run. Lightning Enable asks your wallet for an invoice, and asks it again later whether that invoice was paid. There is no payment provider account in between. :::tip No account, no onboarding This is the fastest lane to start receiving. If you have a wallet that speaks NWC, you can be minting L402 challenges in about a minute — no signup, no API key, no business verification anywhere. ::: Lightning Enable does not hold funds on this lane. Your wallet facilitates custody and settlement; Lightning Enable speaks NIP-47 to it and nothing else. ## What you get, and what you don't NWC is connected as a **receive-only** lane. Lightning Enable never instructs your wallet to send. | Capability | NWC | Strike | OpenNode | |---|---|---|---| | Create Lightning invoices | Yes | Yes | Yes | | Preimage for L402 | Yes | Yes | No | | Payment detection | Polling, up to 60s | Webhook | Webhook | | On-chain addresses | No | Yes | Yes | | Fiat-denominated invoices | No | Yes | No | | Refunds through the API | No | Yes | Yes | Two absences are worth reading twice: - **No webhook.** NIP-47 defines none. Lightning Enable detects payment by asking your wallet (`lookup_invoice`) once a minute, so an invoice can take up to 60 seconds to show as paid. Your own `CallbackUrl` webhook still fires, with the same payload shape the other providers send and `"provider": "nwc"`. - **No refunds.** A refund is an outgoing payment, and this lane never sends. Refund a payer from your wallet app, or connect a [Strike account](../strike-setup/account-setup.md) if you need refunds issued through the API. ## Choose a wallet Lightning Enable speaks **NIP-04** encryption to your wallet, which is the original NIP-47 default and the scheme most deployed wallets accept. Known to work: [CoinOS](https://coinos.io), [Alby](https://getalby.com) (NIP-04 connections), and other wallets that publish NIP-04 support in their NIP-47 capabilities. A wallet that requires NIP-44 encryption is not supported on this lane. It shows up as an invoice request that times out after 30 seconds with a message naming the encryption mismatch, rather than as a silent failure. ## Create the connection 1. Open your wallet app and find its Nostr Wallet Connect (or "app connections") screen. 2. Create a new connection for Lightning Enable. 3. Grant it the `make_invoice` and `lookup_invoice` permissions. Lightning Enable calls nothing else, so do not grant `pay_invoice` — the connection cannot spend, and it should not be able to. 4. Set a budget of zero if your wallet offers one. A receive-only connection needs no spending allowance. 5. Copy the connection string. It looks like this: ``` nostr+walletconnect://?relay=wss://&secret= ``` :::warning Treat this string as a credential The `secret` in that string authorises NIP-47 calls against your wallet. Store it the way you would store an API key. Lightning Enable encrypts it at rest with AES-256-GCM and never returns it from any endpoint — the settings API answers `` and nothing more. ::: ## The fastest path: the MCP tool If you're driving setup through the Lightning Enable MCP server (for example, via the [`producer-setup` skill](/getting-started/sell-with-your-agent)), one tool call replaces the two REST calls below: ```text l402_producer(action="configure_receive", nwcConnectionString="nostr+walletconnect://...") ``` This saves the connection string and switches your account onto the NWC lane in one step. Follow it with `l402_producer(action="status")` to confirm the lane is live before you mint anything against it — see [Sell With Your Agent](/getting-started/sell-with-your-agent) for the full flow. ## Save it in Lightning Enable Prefer to do it by hand? Save the string with your merchant API key: ```bash curl -X PUT https://api.lightningenable.com/api/merchant/nwc-connection \ -H "X-API-Key: $LIGHTNING_ENABLE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "nwcConnectionString": "nostr+walletconnect://?relay=wss://&secret=" }' ``` ```json { "success": true, "message": "Nostr Wallet Connect connection saved. Lightning Enable will create invoices on your wallet and poll it for payment — no webhook is needed.", "nwcConnectionString": "" } ``` The connection string is validated before it is stored, so a malformed string comes back as a `400` instead of failing later on your first real payment. Saving a connection string also puts your account on the NWC lane if you had not chosen a provider yet. To switch lanes explicitly: ```bash curl -X PUT https://api.lightningenable.com/api/merchant/payment-provider \ -H "X-API-Key: $LIGHTNING_ENABLE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"provider": "nwc"}' ``` ## Check it works Mint a challenge. A successful mint proves the whole path: Lightning Enable reached your relay, your wallet decrypted the request, and it returned a real BOLT11 invoice. ```bash curl -X POST https://api.lightningenable.com/api/l402/challenges \ -H "X-API-Key: $LIGHTNING_ENABLE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"resource": "/hello", "priceSats": 10, "description": "NWC smoke test"}' ``` Pay the invoice from another wallet, then wait up to a minute. The invoice flips to `paid`, your `CallbackUrl` receives the webhook, and the preimage becomes available for L402 verification. ## How amounts work NWC settles in Bitcoin, and Lightning Enable performs no currency conversion on this lane. Request invoices in `BTC` (or `SATS`); a `USD` request returns a `400` naming the two options that work. L402 challenges are already priced in satoshis, so they need no change. Invoices are for whole satoshis, with a minimum of 1. ## Troubleshooting **"The wallet did not answer within 30s."** The wallet app is offline, the connection was revoked in the wallet, or the wallet requires NIP-44 encryption. Check the connection is still listed in your wallet app, then re-copy and save the string. **"Could not reach any of the relays in the wallet's NWC connection string."** The relay host in the string is down or unreachable. Some wallets let you regenerate a connection against a different relay; a string that advertises two relays fails over automatically. **"The wallet's reply could not be decrypted."** The connection was rotated in the wallet app, so the secret Lightning Enable holds no longer matches. Create a fresh connection and save the new string. **A paid invoice still shows as unpaid.** Detection on this lane polls once a minute, so allow 60 seconds. Beyond that, confirm the connection still has the `lookup_invoice` permission — a connection granted only `make_invoice` can create invoices it can never confirm. ## See also - [Sell With Your Agent](/getting-started/sell-with-your-agent) — zero to a paid, agent-discoverable endpoint using this connection, driven end to end by an MCP agent - [Strike account setup](../strike-setup/account-setup.md) — the default lane, with webhooks, fiat invoices, and refunds - [L402 Producer API](../products/agentic-commerce/l402-producer-api.md) — minting and verifying paid access once your wallet is connected ============================================================================== # Account Setup Source: https://docs.lightningenable.com/opennode-setup/account-setup ============================================================================== # OpenNode Account Setup :::warning Legacy provider OpenNode is a legacy per-merchant option. Strike is the default payment provider, and new accounts should start there. OpenNode does not surface payment preimages on outgoing payments, which makes it a poor fit for L402 work and means refunds behave differently. Receiving L402 payments does work on OpenNode. Unless you have a specific reason to stay, use [Strike](/strike-setup/account-setup). ::: OpenNode is a payment software provider that facilitates Bitcoin custody, settlement, and KYB compliance. Lightning Enable connects to your OpenNode account to create invoices and process payments. :::warning OpenNode KYB Required You must complete OpenNode's KYB (Know Your Business) verification before accepting live payments. This is required by regulation and cannot be bypassed. ::: ## Why OpenNode? OpenNode provides: - **Lightning Network support** - Instant Bitcoin payments - **Automatic conversion** - Convert to fiat if desired - **Bank settlements** - Withdraw to your bank account - **KYB/AML compliance** - Regulatory compliance handled for you - **24/7 uptime** - Enterprise-grade reliability Lightning Enable uses OpenNode as the underlying payment software provider, giving you: - API middleware architecture (we never touch funds - OpenNode facilitates custody and settlement) - Bring Your Own API Key (BYOA) model - Full control over your payment settings ## Create OpenNode Account ### Step 1: Sign Up 1. Visit [app.opennode.com/signup](https://app.opennode.com/signup/) 2. Click **"Get Started"** or **"Sign Up"** 3. Enter your email and create a password 4. Verify your email address ### Step 2: Choose Account Type Select the appropriate account type: | Type | Best For | |------|----------| | **Individual** | Freelancers, solo developers | | **Business** | Companies, e-commerce platforms | | **Enterprise** | High-volume merchants, platforms | ### Step 3: Complete KYB Verification OpenNode requires business verification to comply with financial regulations. **Individual Accounts:** - Government-issued ID (passport, driver's license) - Proof of address (utility bill, bank statement) - Phone number verification **Business Accounts:** - Company registration documents - Proof of business address - Director/owner identification - Bank account verification :::info Verification Time KYB verification typically takes 2-4 business days. While waiting you can explore OpenNode's development/testnet environment directly — but note that Lightning Enable's hosted platform requires a **production** OpenNode key, so end-to-end testing starts once KYB completes. ::: ## Account Dashboard Once verified, your OpenNode dashboard provides: ### Overview - Total received payments - Pending settlements - Account balance - Recent transactions ### Transactions - Full transaction history - Payment details - Refund tracking ### Settings - API key management - Webhook configuration - Settlement preferences - Security settings ## Development vs Production OpenNode provides two environments: ### Development (Testnet) - **URL:** `dev-api.opennode.com` - **Dashboard:** `app.dev.opennode.com` - **Bitcoin:** Testnet (free test bitcoins) - **KYB:** Not required - **Use for:** Testing and development ### Production (Mainnet) - **URL:** `api.opennode.com` - **Dashboard:** `app.opennode.com` - **Bitcoin:** Real mainnet Bitcoin - **KYB:** Required - **Use for:** Live payments :::warning Hosted Platform Uses Production Only Lightning Enable's hosted platform connects to OpenNode's **production** API — development-environment keys cannot be used with it. The dev environment is useful for exploring OpenNode's own API on testnet, but to test your Lightning Enable integration, use a production key and a small real payment. See [Testing](/opennode-setup/testing). ::: ## Account Security ### Enable Two-Factor Authentication 1. Go to **Settings** > **Security** 2. Click **Enable 2FA** 3. Scan QR code with authenticator app 4. Enter verification code 5. Save backup codes securely ### API Key Security - Never share your API keys - Store any copy in a password manager or secret store — the key itself lives encrypted in Lightning Enable once you save it in the dashboard - Rotate keys periodically ### IP Allowlisting OpenNode lets you restrict API access by source IP (**Settings** > **Security**). Do **not** enable this for the key you use with Lightning Enable: API calls to OpenNode come from Lightning Enable's hosted platform, whose outbound IP addresses are not static, so an IP allowlist would intermittently break payments. ## Settlement Configuration ### Bitcoin Holdings Keep received payments in Bitcoin: 1. Go to **Settings** > **Settlement** 2. Select **"Keep in Bitcoin"** 3. Withdraw manually when desired ### Auto-Convert to Fiat Automatically convert to USD/EUR: 1. Go to **Settings** > **Settlement** 2. Select **"Convert to Fiat"** 3. Choose your currency 4. Set conversion percentage (0-100%) ### Bank Settlements Receive fiat to your bank account: 1. Complete business verification 2. Add bank account details 3. Set settlement frequency (daily, weekly) 4. Configure minimum settlement amount ## Troubleshooting ### Account Verification Pending If verification is taking longer than expected: 1. Check email for requests for additional documents 2. Ensure all documents are clear and readable 3. Contact OpenNode support ### Account Locked If your account is locked: 1. Check email for explanation 2. Respond to any compliance requests 3. Contact support@opennode.com ### Test Payments Not Working If test payments through Lightning Enable are failing: 1. Confirm your OpenNode key is a **production** key from [app.opennode.com](https://app.opennode.com) (dev keys don't work with the hosted platform) 2. Validate the key: `POST /api/merchant/validate-opennode` with your merchant API key 3. Check your OpenNode account is active and KYB-verified 4. See [Testing](/opennode-setup/testing) for the full walkthrough ## Next Steps Once your OpenNode account is set up: - [API Keys](/opennode-setup/api-keys) - Generate and configure API keys - [Webhooks](/opennode-setup/webhooks) - Set up payment notifications - [Testing](/opennode-setup/testing) - Test your integration ============================================================================== # API Keys Source: https://docs.lightningenable.com/opennode-setup/api-keys ============================================================================== # OpenNode API Keys :::warning Legacy provider OpenNode is a legacy per-merchant option. Strike is the default payment provider, and new accounts should start there. OpenNode does not surface payment preimages on outgoing payments, which makes it a poor fit for L402 work. Unless you have a specific reason to stay, see [Strike API Keys](/strike-setup/api-keys). ::: API keys authenticate your requests to OpenNode through Lightning Enable. This guide shows how to generate, configure, and secure your API keys. ## API Key Types OpenNode provides different API key types: | Key Type | Permissions | Use Case | |----------|-------------|----------| | **Invoice** | Create invoices, check status | Payment creation | | **Withdrawal** | Send payments, refunds | Refund processing | | **Admin** | Full account access | Account management | :::warning Lightning Enable requires an **Invoice** key for basic payments, or an **Admin** key if you need refund capabilities. ::: ## Generate API Key ### Development Environment :::note Dev-environment keys are useful for exploring OpenNode's own API on testnet, but they **cannot be used with Lightning Enable's hosted platform**, which connects to OpenNode production. Use a production key below when configuring Lightning Enable. ::: 1. Log in to [app.dev.opennode.com](https://app.dev.opennode.com) 2. Navigate to **Integrations** > **API Keys** 3. Click **Generate New Key** 4. Select key type: - **Invoice** for payment creation - **Admin** for full access (including refunds) 5. Copy and securely store the key ### Production Environment 1. Log in to [app.opennode.com](https://app.opennode.com) 2. Navigate to **Integrations** > **API Keys** 3. Click **Generate New Key** 4. Select key type 5. Copy and securely store the key :::danger API keys are shown only once. If you lose it, you'll need to generate a new one. ::: ## Configure in Lightning Enable ### Via Dashboard Sign in to your Lightning Enable dashboard and navigate to **Settings → Payment Provider** to paste your OpenNode API key. ### Via Merchant API ```bash curl -X PUT https://api.lightningenable.com/api/merchant/opennode-key \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{ "apiKey": "your-opennode-api-key" }' ``` :::note These are the only two configuration methods Your OpenNode key is stored by Lightning Enable's hosted platform, encrypted at rest with AES-256-GCM. There is no environment variable or configuration file to set — the dashboard and the `PUT /api/merchant/opennode-key` endpoint above are the only ways to configure it. ::: :::info Production keys only The hosted platform connects to OpenNode's production API, so configure a **production** OpenNode key (from [app.opennode.com](https://app.opennode.com)). Development-environment keys will fail validation. ::: ## API Key Security - Never commit your OpenNode key to source control or paste it into client-side code — the only place it belongs is the Lightning Enable dashboard or the `PUT /api/merchant/opennode-key` call. - If you keep a copy, store it in a password manager or secret store. - Lightning Enable never returns your OpenNode key back out of the API once saved. - If you suspect the key leaked, revoke it in the OpenNode dashboard immediately and configure a new one. ## Key Rotation Regularly rotate API keys for security: ### When to Rotate - Every 90 days (recommended) - After employee departure - If key may be compromised - After security incident ### Rotation Process 1. **Generate new key** in the OpenNode dashboard 2. **Update the key in Lightning Enable** — dashboard (Settings → Payment Provider) or: ```bash curl -X PUT https://api.lightningenable.com/api/merchant/opennode-key \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{ "apiKey": "your-new-opennode-api-key" }' ``` 3. **Validate the new key**: ```bash curl -X POST https://api.lightningenable.com/api/merchant/validate-opennode \ -H "X-API-Key: your-merchant-api-key" ``` 4. **Verify functionality** with a small test payment 5. **Revoke the old key** in the OpenNode dashboard The update takes effect immediately — subsequent payments use the new key, so there is no downtime window to manage. ## Verify API Key Test your API key is working: ### Using cURL ```bash # Development curl -X GET https://dev-api.opennode.com/v1/account/balance \ -H "Authorization: your-api-key" # Production curl -X GET https://api.opennode.com/v1/account/balance \ -H "Authorization: your-api-key" ``` ### Expected Response ```json { "data": { "balance": { "BTC": "0.00123456", "USD": "52.34" } } } ``` ### Error Response (Invalid Key) ```json { "success": false, "message": "Invalid API key" } ``` ## API Key Permissions ### Invoice Key Capabilities - Create Lightning invoices - Check invoice status - List transactions - Get exchange rates ### Admin Key Capabilities Everything in Invoice key, plus: - Create withdrawals - Process refunds - Manage webhooks - Access account settings ### Minimal Permissions Use the least privileged key for your use case: | Feature | Required Key | |---------|--------------| | Accept payments | Invoice | | Check payment status | Invoice | | Process refunds | Admin | | Send payments | Admin | ## Troubleshooting ### Invalid API Key ```json { "error": "Invalid API key" } ``` **Solutions:** 1. Verify key is copied correctly (no extra spaces) 2. Confirm it is a **production** key from [app.opennode.com](https://app.opennode.com) — the hosted platform does not use OpenNode's dev environment 3. Check key hasn't been revoked 4. Generate new key if needed ### Permission Denied ```json { "error": "Insufficient permissions" } ``` **Solutions:** 1. Check key type (Invoice vs Admin) 2. Generate key with appropriate permissions 3. Verify account is fully verified ### Key Not Found ```json { "error": "API key required" } ``` **Solutions:** 1. Check the Authorization header format 2. Verify a key is saved in Lightning Enable (the dashboard, or `GET /api/merchant/me` — check `hasOpenNodeKey`) 3. Re-save the key via the dashboard or `PUT /api/merchant/opennode-key` ## Best Practices Checklist - [ ] Configure the key only via the Lightning Enable dashboard or `PUT /api/merchant/opennode-key` - [ ] Keep any copy in a password manager or secret store, never in source control - [ ] Enable 2FA on OpenNode account - [ ] Use Invoice key for payments (minimal permissions) - [ ] Use Admin key only when refunds needed - [ ] Rotate keys every 90 days - [ ] Monitor for unauthorized usage ## Next Steps - [Webhooks](/opennode-setup/webhooks) - Configure payment notifications - [Testing](/opennode-setup/testing) - Test your integration - [Quick Start](/getting-started/quick-start) - Make your first payment ============================================================================== # Testing Source: https://docs.lightningenable.com/opennode-setup/testing ============================================================================== # Testing Your Integration :::warning Legacy provider This page covers testing against OpenNode, a legacy per-merchant option. Strike is the default payment provider, and new accounts should start there. Unless you have a specific reason to stay on OpenNode, see [Testing Your Strike Integration](/strike-setup/testing). ::: This guide walks you through testing your Lightning Enable integration with OpenNode as your payment provider. ## How Testing Works on the Hosted Platform Lightning Enable is a hosted platform at `api.lightningenable.com`, and it connects to OpenNode's **production** API. That means: - Your OpenNode API key must be a **production** key from [app.opennode.com](https://app.opennode.com) (KYB required). - Test payments are **small real mainnet payments** (e.g., $1). Lightning fees make this cheap, and you keep the funds in your own OpenNode account. - OpenNode's dev/testnet environment ([app.dev.opennode.com](https://app.dev.opennode.com), `dev-api.opennode.com`) is useful for exploring OpenNode's own API directly, but dev keys **cannot** be used with the hosted platform. | Setting | Value | |---------|-------| | OpenNode Dashboard | [app.opennode.com](https://app.opennode.com) | | Lightning Enable API | `https://api.lightningenable.com` | | Bitcoin Network | Mainnet | | KYB Required | Yes (OpenNode requirement) | ## Setup ### Step 1: Configure Your OpenNode Key Save your production OpenNode API key in Lightning Enable — via the dashboard (**Settings → Payment Provider**) or the merchant API: ```bash curl -X PUT https://api.lightningenable.com/api/merchant/opennode-key \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{ "apiKey": "your-opennode-api-key" }' ``` ### Step 2: Validate the Key ```bash curl -X POST https://api.lightningenable.com/api/merchant/validate-opennode \ -H "X-API-Key: your-merchant-api-key" ``` A successful response confirms Lightning Enable can reach OpenNode with your key. ### Step 3: Get a Lightning Wallet You need a Lightning wallet with a small balance to pay test invoices. Any mainnet Lightning wallet works (e.g., Strike, Phoenix, Breez, Wallet of Satoshi, or your own node). ## Test Payment Flow ### Create Test Payment ```bash curl -X POST https://api.lightningenable.com/api/payments \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{ "orderId": "TEST-001", "amount": 1.00, "currency": "USD", "description": "Test payment" }' ``` Response: ```json { "invoiceId": "inv_test123", "orderId": "TEST-001", "status": "unpaid", "lightningInvoice": "lnbc10u1p...", "amountSats": 2500 } ``` ### Pay Test Invoice 1. Copy the `lightningInvoice` value 2. Open your Lightning wallet 3. Paste or scan the invoice 4. Confirm payment ### Verify Payment Check payment status: ```bash curl https://api.lightningenable.com/api/payments/inv_test123 \ -H "X-API-Key: your-merchant-api-key" ``` Response after payment: ```json { "invoiceId": "inv_test123", "orderId": "TEST-001", "status": "paid", "paidAt": "2026-06-29T12:05:00Z" } ``` ## Test Webhooks ### Local Webhook Testing Lightning Enable delivers webhooks to *your* server. To receive them on your development machine before your production endpoint exists, expose your local webhook handler with ngrok: ```bash # Start your webhook handler locally (whatever stack it runs on), # then expose it: ngrok http 3000 ``` Configure the ngrok URL as your webhook endpoint via the merchant self-service API: ```bash curl -X PUT https://api.lightningenable.com/api/merchant/webhook-url \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{ "webhookUrl": "https://abc123.ngrok.io/webhooks/lightning" }' ``` ### Verify Webhook Received After a test payment, check your server logs for: ``` POST /webhooks/lightning { "event": "payment.completed", "data": { "invoiceId": "inv_test123", "status": "paid" } } ``` ## Test Refunds ### Create Refund First, generate a Lightning invoice from your wallet (for the refund amount), then: ```bash curl -X POST https://api.lightningenable.com/api/refunds \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{ "invoiceId": "inv_test123", "amount": 1.00, "currency": "USD", "lightningInvoice": "lnbc..." }' ``` ### Verify Refund ```bash curl https://api.lightningenable.com/api/refunds/ref_xyz123 \ -H "X-API-Key: your-merchant-api-key" ``` ## Test L402 (Optional) ### Create L402 Proxy ```bash curl -X POST https://api.lightningenable.com/api/proxy \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{ "name": "Test API", "targetBaseUrl": "https://httpbin.org", "defaultPriceSats": 10 }' ``` ### Test L402 Flow ```bash # Get 402 challenge curl https://api.lightningenable.com/l402/proxy/test-api/get # Pay the invoice from response # ... # Access with L402 credential curl https://api.lightningenable.com/l402/proxy/test-api/get \ -H "Authorization: L402 :" ``` ## Testing Checklist ### Basic Integration - [ ] Create payment successfully - [ ] Receive Lightning invoice - [ ] Pay invoice with a Lightning wallet - [ ] Status updates to "paid" - [ ] Webhook received ### Webhook Integration - [ ] Webhook endpoint accessible - [ ] Signature verification works - [ ] Events processed correctly - [ ] Duplicate handling works ### Error Handling - [ ] Invalid API key returns 401 - [ ] Invalid request returns 400 - [ ] Not found returns 404 - [ ] Errors have proper format ### Refunds (if applicable) - [ ] Create refund successfully - [ ] Refund received in wallet - [ ] Webhook notification sent ## Common Test Scenarios ### Test Expired Invoice ```javascript // Create invoice with short expiry const payment = await createPayment({ orderId: 'TEST-EXPIRE', amount: 1.00, currency: 'USD' }); // Wait for expiration (default 60 minutes) // Or check status after expiry const status = await getPayment(payment.invoiceId); // status.status === 'expired' ``` ### Test Multiple Payments ```javascript // Create multiple payments const orders = ['ORDER-1', 'ORDER-2', 'ORDER-3']; for (const orderId of orders) { const payment = await createPayment({ orderId, amount: 1.00, currency: 'USD' }); console.log(`Created: ${payment.invoiceId}`); } ``` ### Test Partial Refund ```javascript // Original payment: $10 const payment = await createPayment({ orderId: 'ORDER-PARTIAL', amount: 10.00, currency: 'USD' }); // Pay the invoice... // Partial refund: $3 const refund1 = await createRefund({ invoiceId: payment.invoiceId, amount: 3.00, currency: 'USD', lightningInvoice: 'lnbc...' }); // Another partial refund: $5 const refund2 = await createRefund({ invoiceId: payment.invoiceId, amount: 5.00, currency: 'USD', lightningInvoice: 'lnbc...' }); ``` ## Debugging ### Check OpenNode Dashboard 1. Log in to [app.opennode.com](https://app.opennode.com) 2. Go to **Transactions** 3. Find your test payments 4. View status and details ### Verify Webhook Delivery Check **your own server logs** to confirm the webhook POST arrived with a valid `X-LightningEnable-Signature` header. If a webhook didn't arrive (or you missed it), don't wait — reconcile against the authoritative status: ```bash # Authoritative payment status curl https://api.lightningenable.com/api/payments/{invoiceId} \ -H "X-API-Key: your-merchant-api-key" # Or force a re-check against your payment provider curl -X POST https://api.lightningenable.com/api/payments/{invoiceId}/sync \ -H "X-API-Key: your-merchant-api-key" ``` ## Going Live After successful testing: 1. **Update your webhook URL** from the ngrok tunnel to your production endpoint (`PUT /api/merchant/webhook-url`) 2. **Verify signature validation** is enabled on your production webhook handler 3. **Run one more small real payment** against the production endpoint 4. **Monitor initial transactions** in the OpenNode dashboard and your own logs ## Next Steps - [Quick Start](/getting-started/quick-start) - Integration guide - [Payments API](/api-reference/payments) - API reference - [Webhooks](/api-reference/webhooks) - Webhook documentation ============================================================================== # Webhook Configuration Source: https://docs.lightningenable.com/opennode-setup/webhooks ============================================================================== # Webhook Configuration :::warning Legacy provider This page covers webhooks for OpenNode, a legacy per-merchant option. Strike is the default payment provider, and new accounts should start there. Unless you have a specific reason to stay on OpenNode, see [Strike Webhooks](/strike-setup/webhooks). ::: Webhooks provide real-time notifications when payment events occur. This guide explains how webhooks flow through the system and how to configure them. ## Webhook Flow ``` Customer pays invoice ↓ OpenNode confirms payment ↓ OpenNode sends webhook to Lightning Enable ↓ Lightning Enable processes and forwards to your endpoint ↓ Your server fulfills the order ``` ## Architecture Lightning Enable acts as a webhook proxy: 1. **OpenNode → Lightning Enable:** OpenNode sends payment notifications 2. **Lightning Enable → Your Server:** We forward events to your configured endpoint This provides: - Consistent webhook format across payment providers - Signature verification - Retry handling - Event logging ## Configure Webhooks ### Step 1: Set Your Webhook URL Configure your endpoint from the Lightning Enable dashboard (**Settings → Webhooks**), or via the merchant self-service API: ```bash curl -X PUT https://api.lightningenable.com/api/merchant/webhook-url \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{ "webhookUrl": "https://your-site.com/webhooks/lightning", "webhookSecret": "your-webhook-secret" }' ``` ### Step 2: OpenNode Webhook (Automatic) Lightning Enable automatically configures OpenNode to send webhooks to our endpoint. No manual configuration needed in OpenNode dashboard. ### Step 3: Implement Webhook Handler Create an endpoint to receive webhooks: ```javascript // Express.js example -- use express.raw() for raw body access app.post('/webhooks/lightning', express.raw({ type: 'application/json' }), (req, res) => { const signatureHeader = req.headers['x-lightningenable-signature']; const payload = req.body.toString('utf8'); // Verify signature (see Signature Verification section below) if (!signatureHeader || !verifySignature(payload, signatureHeader, WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(payload); // Handle event — the payload is a flat object; route on the status field switch (event.status) { case 'paid': handlePaid(event); break; case 'expired': handleExpired(event); break; default: // processing, underpaid, etc. — log and wait for a terminal status console.log('Payment update:', event.invoiceId, event.status); } res.status(200).send('OK'); }); ``` ## Webhook Secret The webhook secret is used to verify that webhooks are from Lightning Enable. ### Generate a Secret Generate a secure random string: ```bash # Using OpenSSL openssl rand -hex 32 # Using Python python -c "import secrets; print(secrets.token_hex(32))" # Using Node.js node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" ``` ### Store Securely ```bash # Environment variable WEBHOOK_SECRET=your-64-character-hex-string ``` ## Signature Verification Always verify webhook signatures to ensure authenticity and prevent replay attacks. ### Signature Format Lightning Enable sends an `X-LightningEnable-Signature` header with every webhook: ```http X-LightningEnable-Signature: t=1704067200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8f9 ``` | Component | Description | |-----------|-------------| | `t` | Unix timestamp (seconds) when the signature was generated | | `v1` | HMAC-SHA256 hex digest of the signed payload | ### How Verification Works 1. **Parse** the `t` (timestamp) and `v1` (signature) from the header 2. **Check freshness** -- reject if the timestamp is more than 5 minutes old (replay protection) 3. **Compute** HMAC-SHA256 of `{timestamp}.{raw_body}` using your webhook secret 4. **Compare** the result with `v1` using a constant-time comparison ### JavaScript Verification ```javascript const crypto = require('crypto'); const TOLERANCE_SECONDS = 300; // 5 minutes function verifySignature(payload, signatureHeader, secret) { // Parse "t={timestamp},v1={signature}" const parts = signatureHeader.split(','); let timestamp = null; let signature = null; for (const part of parts) { const trimmed = part.trim(); if (trimmed.startsWith('t=')) timestamp = parseInt(trimmed.slice(2), 10); else if (trimmed.startsWith('v1=')) signature = trimmed.slice(3); } if (!timestamp || !signature) return false; // Replay protection const now = Math.floor(Date.now() / 1000); if (Math.abs(now - timestamp) > TOLERANCE_SECONDS) return false; // Compute HMAC over "{timestamp}.{payload}" const signedPayload = `${timestamp}.${payload}`; const expected = crypto.createHmac('sha256', secret).update(signedPayload).digest('hex'); return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); } ``` ### C# Verification ```csharp using System.Security.Cryptography; using System.Text; public static bool VerifySignature(string payload, string signatureHeader, string secret) { // Parse "t={timestamp},v1={signature}" long timestamp = 0; string providedSignature = ""; foreach (var part in signatureHeader.Split(',')) { var trimmed = part.Trim(); if (trimmed.StartsWith("t=") && long.TryParse(trimmed[2..], out var t)) timestamp = t; else if (trimmed.StartsWith("v1=")) providedSignature = trimmed[3..].ToLowerInvariant(); } if (timestamp == 0 || string.IsNullOrEmpty(providedSignature)) return false; // Replay protection (5-minute tolerance) var age = DateTimeOffset.UtcNow - DateTimeOffset.FromUnixTimeSeconds(timestamp); if (age.TotalSeconds > 300 || age.TotalSeconds < -30) return false; // Compute HMAC over "{timestamp}.{payload}" var signedPayload = $"{timestamp}.{payload}"; using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(signedPayload)); var expected = Convert.ToHexString(hash).ToLowerInvariant(); return CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(providedSignature)); } ``` ### Python Verification ```python import hmac import hashlib import time TOLERANCE_SECONDS = 300 # 5 minutes def verify_signature(payload, signature_header, secret): """Verify X-LightningEnable-Signature with replay protection.""" timestamp = None signature = None for part in signature_header.split(','): trimmed = part.strip() if trimmed.startswith('t='): timestamp = int(trimmed[2:]) elif trimmed.startswith('v1='): signature = trimmed[3:] if timestamp is None or signature is None: return False # Replay protection now = int(time.time()) if abs(now - timestamp) > TOLERANCE_SECONDS: return False # Compute HMAC over "{timestamp}.{payload}" signed_payload = f'{timestamp}.{payload}' expected = hmac.new( secret.encode('utf-8'), signed_payload.encode('utf-8'), hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected) ``` ## Webhook Payload Lightning Enable forwards a **flat JSON object** per payment event — there is no envelope and no `event` field. For OpenNode-configured merchants the payload is: ```json { "invoiceId": "1042", "orderId": "ORDER-12345", "status": "paid", "amount": 49.99, "currency": "USD", "openNodeChargeId": "abc123-def456-...", "paidAt": "2026-07-03T12:05:00Z", "metadata": "{\"customerId\":\"cust_42\"}" } ``` - `status` is the payment status — route on it. `paid` is the fulfillment trigger; you may also see `processing`, `expired`, `underpaid`, and `refunded`. - `invoiceId` is the Lightning Enable invoice ID (numeric string) from `POST /api/payments`. - `metadata` is the metadata **JSON string** you supplied at payment creation (or `null`). - Refunds do not generate merchant webhooks — poll `GET /api/refunds/{refundId}` instead. (If your merchant account uses Strike instead, the payload carries `providerChargeId` and `provider: "strike"` in place of `openNodeChargeId` — see [Webhooks API reference](/api-reference/webhooks).) ## Webhook Requirements ### Endpoint Requirements - **HTTPS** in production (HTTP allowed for localhost) - Return **200-299** status code within **10 seconds** (the delivery request times out after 10s) - Accept **POST** requests with **JSON** body ### Headers Sent | Header | Description | |--------|-------------| | `Content-Type` | `application/json` | | `X-LightningEnable-Signature` | Timestamped HMAC-SHA256 signature (`t={timestamp},v1={hmac}`) | ## Delivery & Reliability Treat webhooks as a fast-path notification, not a guaranteed delivery channel: - Each delivery attempt has a **10-second timeout**; the first attempt fires as soon as Lightning Enable processes OpenNode's webhook. - If your endpoint is down or errors, delivery is retried with exponential backoff — 30s, 60s, 120s, 240s, 480s (5 retry attempts, ~16-minute total window) — with **identical payload bytes** on every attempt (each attempt's `X-LightningEnable-Signature` is freshly timestamped and verifies against that same body — dedupe on payload content like `invoiceId` + `status`, never on the signature header) *(as of the July 2026 update; earlier versions did not retry failed forwards)*. - After retries exhaust, the event is marked permanently failed — recover by polling. **Recovery pattern:** reconcile pending orders via `GET /api/payments/{invoiceId}` (authoritative), or force a provider re-check with `POST /api/payments/{invoiceId}/sync`. ## Testing Webhooks ### Local Development Use ngrok to expose your local server: ```bash # Terminal 1: Start your server npm start # http://localhost:3000 # Terminal 2: Start ngrok ngrok http 3000 # Copy the ngrok URL (e.g., https://abc123.ngrok.io) # Use as your webhook URL ``` ### Send Test Webhook To send a properly signed test webhook: ```bash # Set your variables WEBHOOK_SECRET="your-webhook-secret" TIMESTAMP=$(date +%s) PAYLOAD='{"invoiceId":"1042","orderId":"TEST-001","status":"paid","amount":10.00,"currency":"USD","openNodeChargeId":"test-charge","paidAt":"2026-07-03T12:05:00Z","metadata":null}' # Compute the signature SIG=$(echo -n "${TIMESTAMP}.${PAYLOAD}" | openssl dgst -sha256 -hmac "${WEBHOOK_SECRET}" | cut -d' ' -f2) # Send the webhook curl -X POST https://your-ngrok-url.ngrok.io/webhooks/lightning \ -H "Content-Type: application/json" \ -H "X-LightningEnable-Signature: t=${TIMESTAMP},v1=${SIG}" \ -d "${PAYLOAD}" ``` ### Webhook Testing Checklist - [ ] Endpoint returns 200 status - [ ] Signature verification works - [ ] Events are processed correctly - [ ] Duplicate events handled (idempotency) - [ ] Errors are logged - [ ] Timeout handling (return 200 quickly) ## Troubleshooting ### Webhooks Not Received 1. **Check URL** - Verify webhook URL is correct 2. **Check HTTPS** - Production requires HTTPS 3. **Check firewall** - Allow incoming connections 4. **Check your server logs** - and reconcile missed events via `POST /api/payments/{invoiceId}/sync` ### Signature Mismatch 1. **Raw body** - Use the raw request body, not re-serialized JSON 2. **Correct secret** - Verify webhook secret matches what you configured 3. **Correct header** - The header is `X-LightningEnable-Signature`, not `X-Webhook-Signature` 4. **Signed payload format** - HMAC is computed over `{timestamp}.{payload}`, not just the payload 5. **Replay protection** - Ensure your tolerance is at least 5 minutes 6. **Encoding** - Use UTF-8 encoding ### Timeouts 1. **Async processing** - Return 200 immediately 2. **Queue jobs** - Process heavy work in background 3. **Check latency** - Optimize endpoint response time ```javascript // Good - Return immediately, process async app.post('/webhooks/lightning', (req, res) => { // Acknowledge receipt immediately res.status(200).send('OK'); // Process asynchronously setImmediate(() => { processWebhook(req.body); }); }); ``` ### Duplicate Events Implement idempotency using the invoice ID: ```javascript async function handlePaid(payload) { const { invoiceId } = payload; // Check if already processed const order = await db.orders.findOne({ lightningInvoiceId: invoiceId }); if (order.status === 'fulfilled') { console.log('Already processed:', invoiceId); return; } // Process and mark as fulfilled await fulfillOrder(order.id); await db.orders.update(order.id, { status: 'fulfilled' }); } ``` ## Best Practices 1. **Return 200 quickly** - Acknowledge receipt, process later 2. **Verify signatures** - Always validate HMAC 3. **Handle duplicates** - Webhooks may be sent multiple times 4. **Log everything** - Keep records for debugging 5. **Secure endpoint** - Use HTTPS, validate signatures 6. **Test thoroughly** - Use ngrok for local testing ## Next Steps - [Testing](/opennode-setup/testing) - Test your integration - [Webhooks API](/api-reference/webhooks) - API reference - [First Payment](/getting-started/first-payment) - Complete a test payment ============================================================================== # Premium Guides Source: https://docs.lightningenable.com/premium-guides ============================================================================== # Premium Guides Deep-dive technical guides for serious builders. Pay once with Lightning, access forever.
## Build L402 from Scratch **$3** (sats computed live at the BTC/USD rate when you request a 402) Implement the L402 protocol yourself, no platform required: - Protocol deep dive (macaroons, payment hashes, preimages) - Full implementations in Node.js, Python, and Go - Lightning node integration (LND) - Production hardening (replay prevention, rate limiting, key management) Buy with Lightning ($3) --- ## Building Revenue-Positive Agents **$3** (sats computed live at the BTC/USD rate when you request a 402) The complete economics guide for AI agents that earn more than they spend: - Cost modeling for AI services - Pricing strategies - Budget policy implementations - Real P&L examples - Path to self-sustaining agents Buy with Lightning ($3) --- ## Advanced NWC Implementation **$3** (sats computed live at the BTC/USD rate when you request a 402) Production-grade NWC patterns for agents that pay at scale: - Custom relay infrastructure (strfry, nginx + TLS) - High-throughput payment orchestration (connection pooling, concurrency) - NIP-04 vs NIP-44 v2 encryption compatibility (CoinOS gotcha and others) - Multi-wallet failover with circuit breakers, gated on preimage support - Monitoring, health checks, and structured logging - Security hardening (secrets management, domain allowlisting, spending guards) Buy with Lightning ($3) --- ## Multi-Agent Payment Networks **$3** (sats computed live at the BTC/USD rate when you request a 402) Agents delegating budgets to sub-agents, hierarchical spending policies, and inter-agent commerce: - Hierarchical budget delegation with revocation - Inter-agent L402 payment routing - Spending policy trees (org → team → agent) - Audit trails and reconciliation - Complete research coordinator example with 5 specialist agents Buy with Lightning ($3) --- ## L402 for Streaming and WebSocket APIs **$3** (sats computed live at the BTC/USD rate when you request a 402) Monetize real-time data feeds and persistent connections with L402: - Token refresh patterns for ongoing streams - Metered streaming (pay per minute, message, or MB) - WebSocket L402 handshake (two-phase connection pattern) - Server-Sent Events with periodic re-auth - Prepaid credit model - Complete market data feed example (Node.js server + Python client) Buy with Lightning ($3) --- ## Lightning Payment Compliance Playbook **$3** (sats computed live at the BTC/USD rate when you request a 402) The regulatory and compliance guide for platforms accepting Lightning payments: - MSB/MTL requirements (US), MiCA (EU) post-enforcement reality, global patterns - Custody models and compliance implications - KYB/KYC requirements and enterprise onboarding - Transaction monitoring and reporting thresholds - Tax considerations including Form 1099-DA (live for tax year 2025) - Enterprise checklist (SOC2, PCI-DSS, vendor risk templates) - Template Terms of Service for Lightning payments - Payment processor evaluation framework Buy with Lightning ($3) --- ## Building an L402 Marketplace **$3** (sats computed live at the BTC/USD rate when you request a 402) Build a multi-vendor API marketplace powered by L402 micropayments: - Marketplace proxy architecture with revenue splitting - Vendor onboarding and endpoint verification - Catalog and discovery (REST + agent-friendly format) - Multi-vendor L402 proxy with per-vendor pricing - Rating, trust, and uptime monitoring - Complete 3-vendor marketplace example - Scaling considerations and abuse prevention Buy with Lightning ($3) --- ## Agent Fleet Economics **$3** (sats computed live at the BTC/USD rate when you request a 402) Managing 10-100+ AI agents with centralized budget allocation and fleet-wide P&L: - Centralized budget allocation (equal, performance-weighted, demand-based) - Wallet architecture for fleets (pooled wallet with virtual ledger) - Automated wallet funding with tiered sources - Fleet-wide P&L dashboard - Cost anomaly detection with automatic circuit breakers - Agent lifecycle management (provision, scale, decommission) - Complete 20-agent content agency example - Optimization strategies (34% cost reduction patterns) Buy with Lightning ($3)
--- ## Bundle: All Guides **$20** (sats computed live) — Save $4 Get all eight premium guides at a discount. All prices are USD-denominated; sats are computed at the live BTC/USD rate when the L402 challenge is issued, so you always pay the dollar amount regardless of where BTC moves. Buy Bundle with Lightning ($20) --- ## For AI Agents Agents can purchase directly via L402 protocol: ```bash # 1. Request guide (returns 402 with invoice) curl https://api.lightningenable.com/l402/proxy/claude-data-transform-api-c7d9/building-revenue-positive-agents.md # 2. Pay the Lightning invoice, receive preimage # 3. Access with L402 credential curl https://api.lightningenable.com/l402/proxy/claude-data-transform-api-c7d9/building-revenue-positive-agents.md \ -H "Authorization: L402 {macaroon}:{preimage}" ``` **Available guides via L402** (all $3 USD; sats shown in the 402 response are computed at the live BTC/USD rate): - `/monetize-your-api-in-10-minutes.md` (Build L402 from Scratch) - `/building-revenue-positive-agents.md` - `/nwc-wallet-integration.md` (Advanced NWC Implementation) - `/multi-agent-payment-networks.md` - `/l402-streaming-websocket.md` - `/lightning-payment-compliance.md` - `/building-l402-marketplace.md` - `/agent-fleet-economics.md` - `/bundle` ($20 USD, all eight) --- ## L402 Tool APIs (pay-per-call) In addition to one-time guide purchases, Lightning Enable runs paid utility APIs that agents call repeatedly: | Endpoint | Purpose | Price | |---|---|---| Each endpoint returns 402 with a Lightning invoice and macaroon; pay it and retry with `Authorization: L402 {macaroon}:{preimage}`. --- export const LightningCheckoutStyles = () => ( ); ============================================================================== # AI Agent Integration Source: https://docs.lightningenable.com/products/agentic-commerce/ai-agent-integration ============================================================================== # AI Agent Integration Lightning Enable provides MCP (Model Context Protocol) servers that enable AI agents like Claude to automatically access L402-protected APIs with Lightning payments. :::tip Open-Source MCP Server The MCP server is open-source (MIT) and free to install. Wallet, invoice, L402, budget, and API-discovery tools work out of the box with just a wallet — no account or API key required. Producer tools (sell access via L402) and Agent Service Agreement tools (agent-to-agent commerce over Nostr) unlock with a Lightning Enable API key. See the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the full tool list. ::: ## Overview When an AI agent encounters an L402-protected resource, the MCP server automatically: 1. Detects the 402 Payment Required response 2. Pays the Lightning invoice via your configured wallet (Strike, LND, or NWC — must return preimage for L402) 3. Retries the request with the L402 credential 4. Returns the response to the agent The MCP server can also pay Lightning invoices directly using the `pay_invoice` tool, enabling AI agents to make arbitrary Lightning payments on your behalf. This enables seamless pay-per-request API access without user intervention. ``` User → Claude → MCP Server → L402 API ↓ Lightning Wallet (Strike, LND, or NWC) ``` :::tip L402 Wallet Compatibility L402 auto-pay requires the payment preimage. These wallets work: - **LND (self-hosted)** - Best for guaranteed L402, always returns preimage - **NWC with CoinOS** - Free, easy, returns preimage - **NWC with CLINK** - Nostr users, returns preimage - **Strike** - Easy setup, returns preimage via `lightning.preImage` - **Alby Hub** - You hold the keys, returns preimage These do NOT work for L402 (no preimage return): - **OpenNode** - No preimage - **Primal** - No preimage ::: ## Available Implementations ### Python MCP Server Recommended for Claude Desktop on all platforms. ```bash pip install lightning-enable-mcp ``` :::note NWC wallets Connecting a **Nostr Wallet Connect (NWC)** wallet (like the `NWC_CONNECTION_STRING` config below)? Install the optional extra: `pip install lightning-enable-mcp[nwc]` — or with uvx, `uvx --from "lightning-enable-mcp[nwc]" lightning-enable-mcp`. Other wallet types (LND, Strike, OpenNode) don't need it. ::: Or use uvx (no installation needed): ```json { "mcpServers": { "lightning-enable": { "command": "uvx", "args": ["lightning-enable-mcp"], "env": { "NWC_CONNECTION_STRING": "nostr+walletconnect://..." } } } } ``` ### .NET MCP Server For Windows users or .NET environments: ```bash dotnet tool install -g LightningEnable.Mcp ``` Configuration (for L402, use LND, Strike, or NWC with CoinOS/CLINK): ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "NWC_CONNECTION_STRING": "nostr+walletconnect://..." } } } } ``` ## Configuration ### Wallet Options The MCP server supports multiple wallet providers. Choose one based on your needs: | Wallet | Best For | Setup Complexity | L402 Auto-Pay | |--------|----------|------------------|---------------| | **LND (self-hosted)** | **L402 auto-pay (guaranteed)** | Hard (run a node) | ✅ **Yes** | | **NWC (CoinOS/CLINK)** | **L402 auto-pay (easy)** | Medium (wallet setup) | ✅ **Yes** | | **Alby** | L402 auto-pay | Medium (wallet setup) | ✅ **Yes** | | **Strike** | USD users, everyday spending | Easy (API key) | ✅ **Yes** | | **OpenNode** | Direct payments only | Easy (API key) | ❌ No* | *OpenNode does not return the payment preimage, which is required for L402 credential verification. Use only for direct `pay_invoice` calls. :::info Wallet Priority If multiple wallets are configured, they are used in this order (optimized for L402): 1. LND (if `LND_REST_HOST` + `LND_MACAROON_HEX` are set) 2. NWC (if `NWC_CONNECTION_STRING` is set) 3. Strike (if `STRIKE_API_KEY` is set) 4. OpenNode (if `OPENNODE_API_KEY` is set) Override with `WALLET_PRIORITY` environment variable or config file `wallets.priority`. ::: ### Option 1: Strike (Recommended for USD Users) Strike is ideal if you prefer managing funds in USD. It provides easy on/off ramps and supports both Bitcoin and USD balances. 1. Create an account at https://strike.me 2. Get your API key from https://dashboard.strike.me 3. Fund your Strike account ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "STRIKE_API_KEY": "your-strike-api-key" } } } } ``` | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `STRIKE_API_KEY` | Yes | - | Strike API key | :::tip Strike L402 Support Strike now returns the payment preimage via `lightning.preImage`, enabling full L402 support. Strike works for both direct payments (`pay_invoice`) and L402 auto-pay (`access_l402_resource`). Ensure your Strike account has BTC balance (payments use BTC, not USD). ::: **Additional Strike features:** - `get_balance` - View sats plus all currency balances (USD and BTC on Strike) - `wallet_ops` with `action="price"` - Get current BTC/USD price (formerly `get_btc_price`) - `wallet_ops` with `action="exchange"` - Convert between USD and BTC (formerly `exchange_currency`) - `wallet_ops` with `action="send_onchain"` - Send on-chain Bitcoin payments (formerly `send_onchain`) ### Option 2: OpenNode (Direct Payments Only) Use your OpenNode account to pay invoices directly. **Note:** OpenNode does not return preimages, so it cannot be used for L402 auto-pay. 1. Get your API key from https://app.opennode.com (or https://dev.opennode.com for testnet) 2. Ensure the API key has **withdrawal permissions** 3. Fund your OpenNode account ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "OPENNODE_API_KEY": "your-opennode-api-key", "OPENNODE_ENVIRONMENT": "dev" } } } } ``` | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `OPENNODE_API_KEY` | Yes | - | OpenNode API key with withdrawal permissions | | `OPENNODE_ENVIRONMENT` | No | production | `production` for mainnet, `dev` for testnet | ### Option 3: Nostr Wallet Connect (NWC) NWC connects the MCP server to a Lightning wallet via the Nostr protocol. L402 compatibility depends on the wallet. :::tip NWC L402 Compatibility | Wallet | L402 Works | Cost | |--------|------------|------| | **CoinOS** | ✅ Yes | Free | | **CLINK** | ✅ Yes | Free (Nostr users) | | **Alby** | ✅ Yes | Self-host or paid cloud | | **Primal** | ❌ No | Free (no preimage) | ::: | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `NWC_CONNECTION_STRING` | Yes | - | Nostr Wallet Connect URI | ### Getting an NWC Connection String Available NWC providers: | Wallet | L402 | Setup | |--------|------|-------| | [CoinOS](https://coinos.io) | ✅ Yes | Settings → NWC (Free, recommended) | | [CLINK](https://clink.tools) | ✅ Yes | Nostr-native wallet | | [Alby Hub](https://albyhub.com?ref=magma) | ✅ Yes | Dashboard → Connections | | [Primal](https://primal.net) | ❌ No | Settings → Wallet → NWC (direct payments only) | The connection string format: ``` nostr+walletconnect://?relay=&secret= ``` ### Claude Desktop Setup (For L402 - Use LND or NWC) Add to your Claude Desktop config: **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` **Linux:** `~/.config/claude/claude_desktop_config.json` **Option A: LND (Guaranteed L402)** ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "LND_REST_HOST": "localhost:8080", "LND_MACAROON_HEX": "your-admin-macaroon-in-hex" } } } } ``` **Option B: NWC with CoinOS (Free, Easy)** ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "NWC_CONNECTION_STRING": "nostr+walletconnect://..." } } } } ``` ## Available Tools The tools below are the ones this integration guide uses most. They are a subset — see the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the full, canonical tool list (including `discover_api`, `test_l402_payment`, `verify_confirmation_code`, `create_lightning_enable_account`, `setup_wallet`, `wallet_ops`, `l402_producer`, and `agent_services`). ### access_l402_resource Fetch a URL with automatic L402 payment handling. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `url` | string | Yes | - | URL to fetch | | `method` | string | No | GET | HTTP method | | `headers` | object | No | {} | Additional headers | | `body` | string | No | - | Request body | | `max_sats` | int | No | 1000 | Max payment | | `confirmation_nonce` | string | No | - | Confirmation code from the server console (`confirmationNonce` in .NET). Required on the retry when the first call returned `requiresConfirmation=true` | **Example conversation:** ``` User: Fetch the premium data from https://api.example.com/l402/proxy/data Claude: I'll access that L402-protected resource. [Uses access_l402_resource] The request required 50 sats which was automatically paid. Here's the response: {"data": "premium content..."} ``` ### pay_l402_challenge Manually pay an L402 invoice when you have the components. | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `invoice` | string | Yes | BOLT11 invoice | | `macaroon` | string | No | Base64 macaroon. Omit for MPP (Machine Payments Protocol) mode — invoice + preimage only | | `max_sats` | int | No | Max payment | | `confirmation_nonce` | string | No | Confirmation code from the server console (`confirmationNonce` in .NET). Required on the retry when confirmation was requested | **Returns:** L402 credential `macaroon:preimage` (or the bare preimage in MPP mode) ### get_balance Check connected wallet balance and session spending. ``` User: Check my wallet balance Claude: [Uses get_balance] Wallet Balance: 50,000 sats Session Spending: 150 sats Budget Remaining: 9,850 sats ``` ### receipts List recent L402 payments. Formerly `get_payment_history` (now `receipts` with `source="session"`) and `get_receipts` (now `source="durable"`) — see the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide#receipts) for the full parameter table. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `source` | string | `durable` | `durable` (persistent log) or `session` (this process only) | | `limit` | int | 10 | Max payments to return | ``` User: Show my payment history Claude: [Uses receipts with source="session"] Recent Payments: 1. api.example.com/data - 50 sats - 2 min ago 2. api.weather.com/forecast - 10 sats - 5 min ago Total: 60 sats ``` ### budget View current budget configuration and spending status, or tighten a runtime cap. Formerly the separate `get_budget_status` (now `action="status"`) and `configure_budget` (now `action="tighten"`) tools — see the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide#budget) for the full parameter table. `action="status"` is **read-only**. Budget limits can only be **raised** by editing the config file — an agent can tighten them at runtime via `action="tighten"`, but never raise them. ``` User: What are my current budget limits? Claude: [Uses budget with action="status"] Budget Status: - Config file: ~/.lightning-enable/config.json - Auto-approve: up to $1.00 - Log & approve: $1.00 - $5.00 - Requires approval: $5.00 - $25.00 - Requires amount verification: $25.00 - $100.00 Session: - Spent: $0.45 (4,500 sats) - Remaining: $99.55 Note: AI cannot RAISE budget limits (budget's action="tighten" can only tighten them). Edit config.json to raise limits. ``` :::info AI-Proof Budget System Budget limits are configured in `~/.lightning-enable/config.json`, which AI agents **cannot raise**. The only budget tool, `budget` (`action="tighten"`, formerly the standalone `configure_budget` tool), can solely *tighten* limits — never loosen or bypass them. This prevents AI from increasing its own spending authority. See [AI Spending Security](/products/agentic-commerce/ai-spending-security) for details. ::: ### pay_invoice Pay any Lightning invoice directly and get the preimage as proof of payment. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `invoice` | string | Yes | - | BOLT11 Lightning invoice | | `max_sats` | int | No | 1000 | Max payment allowed (Python package; the .NET tool relies on budget-service limits) | | `confirmation_nonce` | string | No | - | Confirmation code from the server console (`confirmationNonce` in .NET). Required on the retry when the first call returned `requiresConfirmation=true` | **Returns:** Preimage (proof of payment) or error details ``` User: Pay this Lightning invoice: lnbc100n1p3... Claude: [Uses pay_invoice] Payment successful! - Amount: 100 sats - Preimage: 0a1b2c3d... ``` :::warning Security Notice The `pay_invoice` tool enables AI to spend your Bitcoin. This is powerful but requires caution: - **Use a dedicated wallet** with limited funds - **Configure budget limits** before first use - **Review payments** after each session See [AI Spending Security](/products/agentic-commerce/ai-spending-security) for mandatory safeguards. ::: ## Usage Examples ### Accessing Premium APIs ``` User: Get the 7-day weather forecast from the Lightning weather API Claude: I'll fetch that from the L402-protected weather API. [Uses access_l402_resource with url="https://api.example.com/l402/proxy/weather/forecast/7day"] The forecast required a payment of 25 sats. Here's your 7-day forecast: - Monday: Sunny, 72°F - Tuesday: Partly cloudy, 68°F ... ``` ### Research with Multiple Paid APIs ``` User: Research Bitcoin price predictions from premium sources Claude: I'll query several L402-protected data sources. [Uses access_l402_resource multiple times] I accessed 3 paid APIs (total: 150 sats): 1. BitcoinAnalytics - Bullish, target $100k 2. CryptoResearch - Neutral, consolidation expected 3. ChainMetrics - On-chain data suggests accumulation ``` ### Budget-Aware Browsing :::note Operator budget limits are set by editing `~/.lightning-enable/config.json` directly. The `budget` tool's `action="tighten"` (formerly the standalone `configure_budget` tool) can only **tighten** (lower) those limits at runtime — an agent can never raise them above your config-file values, so a prompt-injected agent can't loosen its own caps. Set your session limit in the config file before starting. ::: ``` User: I have a 500 sat budget. Find the best AI coding assistant APIs. Claude: [Checks budget via get_budget_status] [Uses access_l402_resource for multiple APIs, stopping when budget depletes] I evaluated 5 APIs within your 500 sat budget: 1. CodeHelper API (50 sats) - Best for Python 2. AIAssist (100 sats) - Multi-language support ... Remaining budget: 50 sats ``` ### Direct Invoice Payments The `pay_invoice` tool allows AI to pay any Lightning invoice, not just L402 challenges: ``` User: I want to tip this content creator. Pay this invoice: lnbc500n1p3... Claude: [Uses pay_invoice with invoice="lnbc500n1p3..."] Payment successful! - Amount: 500 sats - Preimage: 7f8a9b2c... - Recipient received the payment instantly The preimage proves the payment was completed. ``` ``` User: Buy access to this premium article by paying: lnbc1000n1... Claude: Let me check your budget first. [Uses get_balance] Balance: 5,000 sats - sufficient for this 1,000 sat payment. [Uses pay_invoice with invoice="lnbc1000n1..."] Payment complete! 1,000 sats paid. Here's your preimage for proof: 3d4e5f... You can now access the article. ``` ## Security Best Practices :::danger Critical: Read Before Using pay_invoice The `pay_invoice` tool enables AI to spend real Bitcoin from your wallet. Before first use: 1. Read [AI Spending Security](/products/agentic-commerce/ai-spending-security) - mandatory safeguards 2. Review [Spending Guidelines](/configuration/ai-spending-guidelines) - budget recommendations 3. Understand [Legal Considerations](/configuration/legal-considerations) - liability and terms ::: ### Use a Dedicated Wallet **Never use your main wallet or business funds.** Create a separate wallet specifically for AI spending: ``` Main Wallet (Your Funds) AI Spending Wallet (Separate) ├── Large balance ├── Small balance (< $100) ├── Business funds ├── Only for AI agents └── NEVER for AI spending └── Easy to monitor/refill ``` For users with multiple wallets, create a dedicated wallet specifically for AI spending. ### Configure Budget Limits (Mandatory) Budget limits are configured in `~/.lightning-enable/config.json`. This file is created automatically on first run: ```json { "currency": "USD", "tiers": { "autoApprove": 1.00, "logAndApprove": 5.00, "formConfirm": 25.00, "urlConfirm": 100.00 }, "limits": { "maxPerPayment": 500.00, "maxPerSession": 100.00 } } ``` **Multi-Tier Approval System:** | Amount (USD) | Behavior | |--------------|----------| | ≤ $1.00 | Auto-approved silently | | $1.00 - $5.00 | Logged but approved | | $5.00 - $500.00 | Out-of-band confirmation: the server prints a code to its **console/stderr**; you give the code to the AI, which re-calls the payment tool with it | | > $500.00 | Blocked entirely (`maxPerPayment`) | **How out-of-band confirmation works:** the confirmation code is printed to the server's console — never returned in a tool result — so a prompt-injected agent can't read its own code and self-approve. The agent must ask **you** for the code, then re-call the **original** payment tool with the `confirmationNonce` (.NET) / `confirmation_nonce` (Python) parameter. Codes are bound to the exact amount, tool, and destination. `wallet_ops` with `action="send_onchain"` always requires a code and fails closed if the budget service is unavailable. **Why this is AI-proof:** The config file lives in your home directory. The only budget MCP tool, `budget` (`action="tighten"`, formerly the standalone `configure_budget` tool), can solely *tighten* limits — there is no tool to raise or loosen them. Only you can raise budget limits, by editing the config file. See [AI Spending Security](/products/agentic-commerce/ai-spending-security) for detailed configuration. ### Review Payment History Regularly check what payments are being made: ``` User: Show all payments from today Claude: [Uses get_payment_history with limit=100] ``` ### Monitor Wallet Balance Keep your wallet funded with only what you're willing to spend: ``` User: Check my wallet before we start Claude: [Uses get_balance] Balance: 5,000 sats - sufficient for this session ``` ## Troubleshooting ### "License required for L402 features" (pre-v1.6.0 only) This error only occurs on versions before v1.6.0. All L402 tools are free in v1.6.0 and later. Update your MCP server: ```bash dotnet tool update -g LightningEnable.Mcp ``` ### "NWC wallet not configured" Ensure `NWC_CONNECTION_STRING` is set correctly in your config. ### "Budget check failed" Payment exceeds configured limits. Edit `~/.lightning-enable/config.json` to adjust your limits. ### "Payment failed" Check: - Wallet has sufficient balance - Invoice hasn't expired (usually 10 min) - NWC connection is active ### Connection issues Verify: - Relay URL is accessible - Wallet app is running (for mobile wallets) - NWC connection hasn't been revoked ## Building Your Own Integration ### Python :::note The Python MCP server is invoked as a subprocess by your MCP client (Claude Desktop, etc.) and configured via environment variables. For programmatic use, see the [l402-requests](https://pypi.org/project/l402-requests/) Python package. ::: ```python # pip install l402-requests from l402_requests import AsyncL402Client # Wallet auto-detected from environment variables (LND > NWC > Strike): # LND_REST_HOST + LND_MACAROON_HEX, NWC_CONNECTION_STRING, or STRIKE_API_KEY async with AsyncL402Client() as client: response = await client.get("https://api.example.com/paid-resource") print(response.json()) ``` There is also a synchronous `L402Client` and module-level `l402_requests.get(...)` / `post(...)` helpers. See the [l402-requests package](https://pypi.org/project/l402-requests/) for budget controls and spending introspection. ### .NET ```csharp // dotnet add package L402Requests using L402Requests; // Wallet auto-detected from environment variables (LND > NWC > Strike), // or pass one explicitly: new L402HttpClient(new StrikeWallet("your-api-key")) using var client = new L402HttpClient(new L402Options { MaxSatsPerRequest = 1000 }); var response = await client.GetAsync("https://api.example.com/paid-resource"); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` See the [L402Requests NuGet package](https://www.nuget.org/packages/L402Requests) for budget limits, domain allowlists, and `HttpClientFactory` integration. ## Next Steps ### Complete Documentation - [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) - **Full tool reference, all capabilities, examples** ### Wallet Setup - [Wallet Configuration](/products/agentic-commerce/mcp-wallet-setup) - Detailed wallet setup for Strike, LND, NWC, and OpenNode ### Security (Read First!) - [AI Spending Security](/products/agentic-commerce/ai-spending-security) - Mandatory safeguards for pay_invoice - [Spending Guidelines](/configuration/ai-spending-guidelines) - Budget recommendations by use case - [Legal Considerations](/configuration/legal-considerations) - Liability and terms ### Technical - [Proxy Configuration](/products/agentic-commerce/proxy-configuration) - Create L402 proxies - [L402 API Reference](/api-reference/l402) - API details - [How It Works](/products/agentic-commerce/how-it-works) - Technical overview ============================================================================== # AI Spending Security Source: https://docs.lightningenable.com/products/agentic-commerce/ai-spending-security ============================================================================== # AI Spending Security When using the `pay_invoice` tool, you are authorizing an AI agent to spend Bitcoin on your behalf. This document covers the security considerations, mandatory safeguards, and best practices for safe AI-driven spending. ## Understanding pay_invoice vs L402 | Aspect | L402 (access_l402_resource) | pay_invoice | |--------|----------------------------|-------------| | **Direction** | You RECEIVE payments | You SPEND payments | | **Risk Level** | Low (inbound funds) | High (outbound funds) | | **Use Case** | Monetize your APIs | Pay for external APIs | | **Safeguards Needed** | None (you're receiving) | Budget limits required | **Key Distinction:** L402 is about receiving payments for your content/APIs. The `pay_invoice` tool is about spending your Bitcoin to pay for external services. ## Risk Model ### What Can Go Wrong 1. **Unintended Payments** - AI misinterprets user intent and pays wrong invoice - AI pays more than expected for a service - AI makes multiple payments when one was intended 2. **Malicious Prompt Injection** - Attacker injects payment request into AI context - Phishing-style invoices embedded in content AI reads - Social engineering via prompts 3. **API Key Compromise** - Wallet API key (Strike, OpenNode, etc.) with withdrawal permissions stolen - Key exposed in logs, environment dumps, or screenshots - Key committed to version control 4. **Budget Bypass Attempts** - Attackers try to make payments just under limit - Multiple small payments to drain session budget - Exploiting timing between budget check and payment ## Mandatory Safeguards ### 1. Dedicated Spending Wallet **NEVER use your main wallet or business funds.** Create a dedicated wallet specifically for AI spending: ``` Main Wallet (Your Business) ├── Receives customer payments ├── Large balance └── NEVER used for AI spending AI Spending Wallet (Separate) ├── Only used by AI agents ├── Small balance (< $100 equivalent) └── Easy to monitor and refill ``` **Supported wallets:** - **LND** - Full L402 support (always returns preimage) - **NWC (CoinOS, CLINK)** - Full L402 support (returns preimage) - **Strike** - Full L402 support (returns preimage via `lightning.preImage`) - **NWC (Alby)** - ✅ Works - **OpenNode** - Direct payments only (no L402, no preimage) - **NWC (Primal)** - Direct payments only (no preimage support yet) :::tip L402 Wallet Options L402 auto-pay requires preimage. These wallets work: - **LND** - Guaranteed L402, self-hosted - **CoinOS** (NWC) - Free, web-based - **CLINK** (NWC) - Nostr-native - **Alby Hub** (NWC) - You hold the keys, returns preimage - **Strike** - Easy API key setup, returns preimage **OpenNode and Primal do NOT work for L402** - they don't return preimages. ::: **Why:** If something goes wrong, you only lose what's in the dedicated account. ### 2. Multi-Tier Approval System (AI-Proof) Lightning Enable uses a USD-based multi-tier approval system that **AI agents cannot bypass or modify**. Configuration is stored in a file that only you can edit. #### Configuration File Location ``` ~/.lightning-enable/config.json ``` On Windows: `C:\Users\YourName\.lightning-enable\config.json` **Important:** The MCP server exposes **no tool** to modify this file — the USD approval tiers here can only be changed by you editing it directly. (An agent *can* call `budget` with `action="tighten"` — formerly the standalone `configure_budget` tool — to **tighten** the separate runtime sats caps — lower them, never raise them — but it can never loosen any limit or touch this file. See below.) Caveat: an agent that has **direct shell or filesystem access to the host** could edit the file outside the server — see the threat-model note below. #### Default Configuration On first run, a default config file is created: ```json { "currency": "USD", "tiers": { "autoApprove": 1.00, "logAndApprove": 5.00, "formConfirm": 25.00, "urlConfirm": 100.00 }, "limits": { "maxPerPayment": 500.00, "maxPerSession": 100.00 }, "session": { "cooldownSeconds": 2, "requireApprovalForFirstPayment": false }, "confirmation": { "channel": "stderr" } } ``` :::note Default Thresholds Payments above the auto-approve threshold use out-of-band confirmation: the server delivers a code over the configured channel (`stderr` by default) for the human operator to relay (see [Confirmation Channels](#confirmation-channels) below). The defaults provide a sensible balance between UX and safety; tighten them in your config if you want more payments to require confirmation. ::: #### Sats-Native Budgets The USD tiers and limits above are converted to sats at call time from a live BTC price feed — and the whole system **fails closed** on any USD-denominated check if that feed is unavailable, rather than approving a payment it can't price. If you'd rather set a ceiling directly in sats — nothing to convert, nothing to fail closed on for that specific check — add the sats-native keys alongside or instead of the USD ones: ```json { "limits": { "maxPerPaymentSats": 50000, "maxPerSessionSats": 200000 }, "tiers": { "autoApproveSats": 1000 } } ``` Whichever pair is present for a given check governs that check — the two aren't summed. A sats-native limit is immune to a price-feed outage by construction; any USD-denominated tier you still have configured stays fail-closed as before. `budget(action="tighten")` accepts `maxPerPaymentSats` / `maxPerSessionSats` for runtime tightening regardless of which pair the operator configured in the file. #### How Each Tier Works | Amount (USD) | Tier | Behavior | |--------------|------|----------| | ≤ $1.00 | **Auto-Approve** | Payment proceeds without any notification | | $1.00 - $5.00 | **Log & Approve** | Payment proceeds, logged for your review | | $5.00 - $25.00 | **Form Confirm** | Requires confirmation (see below) | | $25.00 - $100.00 | **URL Confirm** | Requires explicit confirmation with amount verification | | $100.00 - $500.00 | **Out-of-band code** | Requires the console confirmation code (see below) | | > $500.00 | **Deny** | Payment blocked entirely (`maxPerPayment`) | #### Out-of-Band Confirmation (.NET v1.12.10+, Python v1.12.12+) When a payment exceeds the auto-approve threshold, the server delivers a confirmation code over a channel the **human operator** sees, not the AI. The code is **never returned in a tool result**, so a prompt-injected agent can't read its own code and self-approve. **How confirmation works:** 1. The agent calls `pay_invoice` (or `access_l402_resource` / `pay_l402_challenge`) for an amount above the auto-approve threshold. 2. The tool returns a response telling the agent that confirmation is required — but **without** the code. 3. The server delivers the confirmation code over the configured channel (see [Confirmation Channels](#confirmation-channels) below), where you (the human) can read it. 4. You give the code to the AI, which re-calls the **original** tool with its confirmation-nonce parameter (`confirmationNonce` in .NET, `confirmation_nonce` in Python) to proceed. The separate `verify_confirmation_code` tool (renamed from `confirm_payment` in v1.17.0) can *verify* a code (it echoes the amount/tool it authorizes) but does **not** execute the payment — the original tool does. The confirmation code is **bound to the exact amount, the exact tool, AND the exact destination** (the invoice, URL, or on-chain address) it approved — it can't be reused for a different amount or tool, and it can't be redirected to a different invoice or address (destination binding added in v1.12.13). `wallet_ops` with `action="send_onchain"` (formerly the standalone `send_onchain` tool) **always** requires this confirmation, even for small amounts, because on-chain payments are irreversible. It also **fails closed** if the budget service is unavailable. This ensures you keep control over payments above your auto-approve threshold — the AI can't approve its own payment, because the approving code reaches only the channel you configured, which the human (or a system acting for them) reads. #### Confirmation Channels `confirmation.channel` in the config file picks where the code goes: | `confirmation.channel` | Where the code goes | Use it when | |---|---|---| | `stderr` *(default)* | The server's console/stderr — the assumption the rest of this page's threat model rests on | Interactive local use: Claude Code, Claude Desktop, a terminal session | | `refuse` | Nowhere — the payment is refused outright instead of printed anywhere | Non-interactive/hosted contexts, where nothing is reliably watching stderr and printing a code there risks it being readable by the wrong process instead of a human | | `webhook` | POSTed to a webhook URL you configure | A human is notified out-of-band (chat app, pager) instead of watching a terminal | | `file` | Written to a local file the human reads on their own schedule | Headless or scheduled runs with no one watching in real time | **`LIGHTNING_ENABLE_HOSTED=1`** changes the *default* rather than adding a fifth channel: when set, and the process is not attached to a TTY, the effective channel becomes `refuse` instead of `stderr`. This exists because the `stderr` channel's safety depends entirely on a human — not the agent, not a co-located process — being the one who reads the server's console (see the threat-model box below); a hosted, non-interactive deployment can't assume that, so it refuses rather than guess. Set `confirmation.channel` explicitly to `webhook` or `file` if you want a hosted deployment to still deliver codes somewhere instead of refusing. :::caution Threat-model assumption Out-of-band confirmation rests on one assumption: **the AI runtime cannot read the MCP server's console / stderr or centralized logs, and cannot run shell commands to read them.** That holds for the common setup (Claude Desktop / IDE launches the server as a subprocess whose stderr the model never sees). It does **not** hold if the agent shares a shell or host with the server and can `cat` the logs or read the process's stderr — there it could read its own confirmation code, and could also edit `~/.lightning-enable/config.json` directly. For agents with local shell/filesystem access, run the MCP server somewhere the agent can't read its stderr or files (a separate user/host/container). ::: #### Why This Is AI-Proof 1. **File-Based Configuration** - The config lives in your home directory, not in environment variables that could be exposed 2. **Tighten-Only Runtime Changes** - The `budget` tool (`action="tighten"`) can only *lower* the per-request / per-session caps at runtime; an agent can never raise them above the limits in your config file 3. **Out-of-Band Confirmation** - For payments above the auto-approve threshold, the confirmation code is delivered over the configured channel (the human sees it, or in a hosted context the payment is refused by default — see [Confirmation Channels](#confirmation-channels)) and is never returned to the AI, so the AI can't approve its own payments 4. **First Payment Protection** (opt-in) - When `requireApprovalForFirstPayment` is enabled (it is `false` by default, as shown above), the first payment of each session requires explicit confirmation 5. **Cooldown Periods** - Prevents rapid-fire payment attacks #### Customizing Your Limits Edit `~/.lightning-enable/config.json`: ```json { "currency": "USD", "tiers": { "autoApprove": 0.50, // More restrictive: only auto-approve $0.50 "logAndApprove": 2.00, // Log anything above $2.00 "formConfirm": 10.00, // Require confirmation above $10 "urlConfirm": 50.00 // Require amount verification above $50 }, "limits": { "maxPerPayment": 100.00, // Never pay more than $100 in one payment "maxPerSession": 50.00 // Never spend more than $50 per session }, "session": { "cooldownSeconds": 2, "requireApprovalForFirstPayment": false } } ``` :::tip For Maximum Control If you want to require confirmation for ALL payments, set `autoApprove` to `0`. Every payment will then require out-of-band confirmation — the server delivers a code over your configured channel and you relay it to the AI to approve each one. ::: #### Checking Your Budget Status Use the `budget` tool with `action="status"` (read-only; formerly the standalone `get_budget_status` tool) to see current configuration: ``` budget action="status" Response: { "success": true, "message": "Budget configuration is READ-ONLY. Edit ~/.lightning-enable/config.json to change limits.", "tiers": { "autoApproveUsd": 0.10, "formConfirmUsd": 10.00, ... }, "session": { "spentUsd": 0.45, "remainingUsd": 99.55 }, "security": { "aiCanModify": false, "howToChange": "Edit the config.json file directly. AI agents cannot modify budget limits." } } ``` :::note Legacy env vars removed The `L402_MAX_SATS_PER_REQUEST` and `L402_MAX_SATS_PER_SESSION` environment variables have been **removed from both packages** — setting them has no effect. Operator limits live in `~/.lightning-enable/config.json` (`limits.maxPerPayment` / `limits.maxPerSession` in USD, or `limits.maxPerPaymentSats` / `limits.maxPerSessionSats` — see [Sats-Native Budgets](#sats-native-budgets)). The runtime sats caps can only be **tightened** via the `budget` tool's `action="tighten"` (`per_request` / `per_session` in Python, `perRequest` / `perSession` in .NET, or `maxPerPaymentSats` / `maxPerSessionSats`) — never raised. ::: ### 3. API Key Permissions When creating your wallet API key (Strike, OpenNode, etc.) for AI spending: - **Use withdrawal-only permissions** if available - **Create a dedicated key** - don't reuse keys - **Rotate keys regularly** (monthly recommended) - **Never commit keys to version control** - **Never share keys in screenshots or logs** ```bash # MCP Configuration - store key securely (use your wallet of choice) export STRIKE_API_KEY="your-strike-key-here" # or export OPENNODE_API_KEY="your-opennode-key-here" export OPENNODE_ENVIRONMENT="production" ``` ### 4. Monitoring and Auditing Review payments after every AI session: ``` # Use the MCP tool to check payment history get_payment_history limit=20 ``` Set up monitoring: - Enable email notifications for withdrawals in your wallet provider (Strike, OpenNode, etc.) - Review weekly spending totals - Set up balance alerts (notify when balance drops below X) - Check for unexpected payment patterns ## Environment Variable Checklist Before using `pay_invoice`, verify one of the following wallet options is configured: **Strike (recommended):** | Variable | Purpose | Recommended Value | |----------|---------|-------------------| | `STRIKE_API_KEY` | API authentication | API key from dedicated Strike account | **OpenNode (alternative, direct payments only — no L402):** | Variable | Purpose | Recommended Value | |----------|---------|-------------------| | `OPENNODE_API_KEY` | API authentication | Withdrawal-only key from dedicated account | | `OPENNODE_ENVIRONMENT` | Network selection | `production` for mainnet | **NWC (for L402 auto-pay):** | Variable | Purpose | Recommended Value | |----------|---------|-------------------| | `NWC_CONNECTION_STRING` | Wallet connection | Connection string from CoinOS, CLINK, or Alby Hub | **LND (for L402 auto-pay, self-hosted):** | Variable | Purpose | Recommended Value | |----------|---------|-------------------| | `LND_REST_HOST` | LND REST API host | `localhost:8080` | | `LND_MACAROON_HEX` | Admin macaroon | Hex-encoded admin macaroon | **Spending limits:** there are no budget environment variables — the legacy `L402_MAX_SATS_PER_REQUEST` / `L402_MAX_SATS_PER_SESSION` vars were removed. Configure limits in `~/.lightning-enable/config.json` before first use; agents can tighten (only) the runtime sats caps with `budget(action="tighten")`. ## What pay_invoice Should NOT Be Used For **Do NOT use pay_invoice if:** - Your wallet balance exceeds ~100,000 sats (~$100 at 100k sats/$) - You're using production/business funds - The AI will run unattended for long periods - You haven't configured budget limits - You're using a shared or untrusted MCP configuration - You haven't reviewed the AI agent's capabilities **Recommended for:** - Developer testing and prototyping - Light interactive use with supervision - Small, bounded research tasks - Learning and experimentation ## Architecture: We Never Touch Funds Lightning Enable is API middleware — your payment provider facilitates custody and settlement: ``` Your Wallet (Strike, LND, NWC, etc.) Lightning Enable MCP Recipient │ │ │ │ ◄────────── You control ──────────► │ ◄─── Software ───► │ │ │ │ ▼ ▼ ▼ Provider custody Just a tool Receives Your account API middleware payment Your responsibility No access to funds ``` **Key points:** - Lightning Enable does not hold funds — the payment provider facilitates custody and settlement - You provide your own provider API key (BYOA model — Strike, LND, NWC, or OpenNode) - Your payment provider holds your funds (they are licensed and regulated) - We never see, touch, or control your Bitcoin - All compliance/KYB/KYC is between you and your provider ## Incident Response If you suspect unauthorized payments: 1. **Immediately** rotate your wallet API key (Strike, OpenNode, etc.) 2. **Check** payment history in your wallet provider's dashboard 3. **Review** what invoices were paid 4. **Update** MCP configuration with new key 5. **Lower** budget limits 6. **Investigate** how the key may have been compromised ## Security Recommendations Summary | Priority | Action | |----------|--------| | **Critical** | Use dedicated wallet with limited funds | | **Critical** | Configure budget limits BEFORE first use | | **High** | Use withdrawal-only API key | | **High** | Review payment history after sessions | | **Medium** | Rotate API keys monthly | | **Medium** | Set up balance alerts | | **Low** | Log and audit all AI sessions | ## Related Documentation - [Spending Guidelines](/configuration/ai-spending-guidelines) - Recommended budgets by use case - [Legal Considerations](/configuration/legal-considerations) - Liability and terms - [AI Agent Integration](/products/agentic-commerce/ai-agent-integration) - MCP setup guide ============================================================================== # Monetize Your API in 10 Minutes Source: https://docs.lightningenable.com/products/agentic-commerce/api-monetization ============================================================================== # Monetize Your API in 10 Minutes Lightning Enable turns any HTTP API into a paid API. Every request either includes a valid payment token or gets back a 402 with a Lightning invoice. You pick how it sits in the request path. ## Two integration modes — pick one Lightning Enable supports two ways to put L402 in front of your API: | | **Native mode** (recommended for production APIs) | **Proxy mode** (no code changes) | |---|---|---| | Where traffic lives | On your domain, your servers | Routed through `api.lightningenable.com` | | Code changes | One `install` + one line of middleware | None | | Existing auth, rate limiting, observability | Preserved — chain your own middleware around it | LE consumes `Authorization`, harder to pass through | | Custom logic per route | Full control | Limited to what the proxy supports | | Best for | Commercial APIs, anything with sensitive infrastructure | Public APIs, experiments, demos | **If your API is something you already run in production — and especially if it has its own auth — Native mode is the right answer.** That's the path this Quick Start walks. If you want zero code changes and you're fine with traffic going through Lightning Enable, jump to [Alternative: hosted proxy mode](#alternative-hosted-proxy-mode-no-code-changes) below. ## Prerequisites - A Lightning Enable account on **Agentic Commerce** ($49/mo, 30-day free trial via self-serve checkout; card required, no charge until trial ends) or **Agentic Commerce — Business** ([contact us](mailto:support@lightningenable.com) — not purchasable through self-serve checkout; any trial terms are arranged directly). - A payment provider — **Strike** (recommended) or **OpenNode**. Strike supports preimage return for full L402 compatibility. - An existing HTTP API on a supported stack (Node + Express or .NET + ASP.NET Core today; FastAPI and Go on the roadmap — see the [Producer API Reference](./producer-api-reference) for raw HTTP integration in any language). ## Check your setup state at any time If you want to know exactly where you are in the 10-minute setup — what's done, what's still required, what example payload comes next — hit the quick-start endpoint: ```bash curl https://api.lightningenable.com/api/merchant/quickstart \ -H "X-API-Key: YOUR_LE_API_KEY" ``` You get back a state machine with `completedSteps`, `requiredStepsCompleted`, `isReadyForProduction`, and a per-step breakdown. Useful both as a "did I miss something" sanity check and as a programmatic readiness probe. ## Step 1: Configure your payment provider (minutes 1-3) Lightning Enable supports Strike (recommended) and OpenNode. Strike is the default — it supports preimage return for full L402 compatibility and requires no additional environment setup. ### Option A: Strike (recommended) 1. Create an account at [strike.me](https://strike.me) and get your API key from [dashboard.strike.me](https://dashboard.strike.me) 2. Add it to Lightning Enable: ```bash curl -X PUT https://api.lightningenable.com/api/merchant/strike-key \ -H "X-API-Key: YOUR_LE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"strikeApiKey": "your-strike-api-key"}' ``` Response: ```json { "success": true, "message": "Strike API key updated successfully. Webhook will re-register on the next L402 challenge." } ``` ### Option B: OpenNode (alternative) **For testing (no KYB required):** create a dev account at [dev.opennode.com](https://dev.opennode.com), generate an API key, and add it: ```bash curl -X PUT https://api.lightningenable.com/api/merchant/opennode-key \ -H "X-API-Key: YOUR_LE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"openNodeApiKey": "your-opennode-dev-key"}' ``` **For production:** same flow at [opennode.com](https://www.opennode.com). KYB verification takes 2-4 business days; use testnet in the meantime. > **OpenNode environment** (dev vs production) is set platform-wide in your Lightning Enable instance, not per-merchant. Hosted Lightning Enable runs against OpenNode production by default. If you need to switch your hosted account to dev, contact support. ## Step 2: Install the middleware (minutes 4-5) Pick your stack: ### Node.js + Express ```bash npm install l402-express l402-server ``` `l402-server` is the underlying SDK that `l402-express` calls; both are MIT-licensed and the middleware re-exports the SDK's types. ### .NET + ASP.NET Core ```bash dotnet add package L402Server.AspNetCore ``` The underlying `L402Server` SDK is pulled in transitively. Both are MIT-licensed. ### Another stack? The producer API is HTTP — you can integrate from any language. See the [Producer API Reference](./producer-api-reference) for the two endpoints you'll call (`POST /api/l402/challenges` to mint, `POST /api/l402/challenges/verify` to validate). ## Step 3: Add the middleware + set prices (minutes 6-7) One line in your app. Anything mounted under it costs the configured number of sats per request. ### Express ```js import express from "express"; import { l402 } from "l402-express"; const app = express(); // Anything below this costs 100 sats per request app.use("/api/premium", l402({ apiKey: process.env.LIGHTNING_ENABLE_API_KEY, priceSats: 100, })); app.get("/api/premium/weather", (_req, res) => { res.json({ temp: 72 }); }); app.listen(3000); ``` Endpoints **not** mounted under `l402(...)` pass through untouched — mix paid and free routes freely. For variable per-request pricing, pass a function instead of a number: `priceSats: (req) => req.query.model === "premium" ? 500 : 100`. ### ASP.NET Core ```csharp using L402Server.AspNetCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddL402AspNetCore(opts => { opts.ApiKey = builder.Configuration["LightningEnable:ApiKey"]!; }); var app = builder.Build(); app.UseRouting(); app.UseL402(); app.MapControllers(); app.Run(); ``` Then mark any action with `[L402(PriceSats = N)]`: ```csharp [ApiController] [Route("api/premium")] public class PremiumController : ControllerBase { [HttpGet("weather")] [L402(PriceSats = 100)] public IActionResult Weather() => Ok(new { temp = 72 }); } ``` That's the whole integration. For both stacks, the middleware: 1. Reads `Authorization: L402 :` from each request 2. If absent → mints a fresh challenge and returns `402 Payment Required` with the invoice 3. If present → verifies it via Lightning Enable. Valid → call your handler; invalid → respond `401` ## Step 4: Test it (minutes 8-9) ### Try an unpaid request ```bash curl -i https://your-api.example/api/premium/weather ``` You'll get: ```http HTTP/1.1 402 Payment Required Content-Type: application/json WWW-Authenticate: L402 macaroon="AgEL...", invoice="lnbc..." { "error": "Payment Required", "l402": { "macaroon": "AgEL...", "invoice": "lnbc1u1p3...", "amount_sats": 100, "payment_hash": "abc123...", "expires_at": "", "resource": "/api/premium/weather" } } ``` ### Pay and access ```bash # After paying the invoice and extracting the preimage: curl https://your-api.example/api/premium/weather \ -H "Authorization: L402 AgEL...:deadbeef..." # 200 OK — your API's response ``` ## Step 5: Ship it (minute 10) Deploy your app. Your existing API is now a paid API. Give your callers (or their agents) the URL — they hit it, pay 402 invoices, get access. ## How L402 interacts with your existing auth The most common question: **does L402 replace my auth, or sit next to it?** Two cases — both work: - **L402 is your only auth.** The macaroon's caveats (path, amount, merchant ID, expiry) plus preimage verification *are* the authorization. Anyone who pays for that resource gets in. This is the right model for the ~90% case — paid APIs where "they paid" is the only fact you need to enforce. L402 is anonymous-payment by design, so this is what you get out of the box. - **L402 + your existing customer auth, side by side.** They coexist because they live in **different request fields**. L402 owns `Authorization: L402 :`. Your existing auth uses a cookie, `X-API-Key`, a JWT in a separate header, an IP allowlist, mTLS — whatever you're already doing. Chain your own auth middleware around `l402(...)` (Express) or before/after `app.UseL402()` (ASP.NET Core); they don't fight. After successful verification, the middleware exposes the verified credential to your handler: - **Express:** `res.locals.l402` → `{ resource, amountSats, paymentHash }` - **ASP.NET Core:** `HttpContext` items — same fields Useful for usage logging, per-endpoint analytics, fraud detection. The payment hash gives you a stable per-payment identifier without revealing buyer identity. ## Optional: list your API in the registry If you want AI agents to *discover* your paid API (not just successfully call it once they know the URL), publish a manifest in the L402 registry. This step is independent of the middleware and uses the existing proxy management endpoints — see [Setting Up Your Proxy](./proxy-setup-walkthrough) for the manifest and registry portion (skip the upstream URL bits; in Native mode your manifest points at your own host). ## Alternative: hosted proxy mode (no code changes) If you'd rather not touch your application code, Lightning Enable can sit in front of your API as a hosted proxy. You give us your API URL, we give you a `https://api.lightningenable.com/l402/proxy/{your-slug}/` URL that handles the 402 challenge dance and forwards paid requests to your origin. - **Pros:** zero code changes, no deploy required, fastest path to a working paywall - **Cons:** traffic flows through Lightning Enable, your existing `Authorization`-header auth is hard to preserve, customer domain changes Full walkthrough: [Setting Up Your Proxy](./proxy-setup-walkthrough). ## What Lightning Enable handles (both modes) | Concern | Handled | |---------|---------| | Invoice creation | Via your configured payment provider (Strike or OpenNode) | | Payment verification | Preimage validated against payment hash cryptographically | | Token management | Macaroon mint with caveats (path, amount, merchant ID, expiry) | | Tenant isolation | A token for Merchant A can never verify against Merchant B | | Token reuse within validity window | Same payment hash re-verifiable for same resource until expiry (feature, not bug; see [producer API reference](./producer-api-reference#token-reuse-within-the-validity-window)) | | SSRF protection (proxy mode) | Private IPs blocked, target URLs validated | | Analytics | Tracks requests, revenue, endpoint usage | ## Next steps - [Native Integration — Express](./native-integration-express) — full reference for the Node middleware - [Native Integration — ASP.NET Core](./native-integration-aspnet) — full reference for the .NET middleware - [Producer API Reference](./producer-api-reference) — raw HTTP API if you're on a stack we don't have a middleware for yet - [Native Integration overview](./native-integration) — deeper architectural rationale - [Setting Up Your Proxy](./proxy-setup-walkthrough) — full proxy-mode walkthrough if you chose that path ============================================================================== # Buzz Agent Setup Source: https://docs.lightningenable.com/products/agentic-commerce/buzz-agent-setup ============================================================================== # Give Your Buzz Agents a Lightning Wallet Your agents live in a [Buzz](https://github.com/block/buzz) community. This guide gives one of them a Lightning wallet with the Lightning Enable MCP, so it can **discover** services on the agent marketplace, **pay for and consume** them over Lightning, and **publish** its own — all without a credit card or a human in the loop. If you're running the [NostrWolfe bridge](https://github.com/refined-element/nostrwolfe-bridge), your agents can already *see* the marketplace as cards in a Services channel. This guide adds the ability to *act* on it. :::info Open source The MCP server is free and open source: [github.com/refined-element/lightning-enable-mcp](https://github.com/refined-element/lightning-enable-mcp). Available on NuGet, PyPI, and Docker. ::: ## Overview ``` ┌───────────────────────────────────────────────────────────────┐ │ Buzz agent (ACP) │ │ │ discover · pay · consume · publish │ │ ▼ │ │ Lightning Enable MCP (stdio subprocess) │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ NostrWolfe L402 endpoints your Lightning wallet │ │ marketplace (pay-per-call) (LND / NWC / Strike / …) │ └───────────────────────────────────────────────────────────────┘ ``` The agent gets its abilities from the MCP's tools directly — no plugin or skill required. You wire the MCP in once, set a budget, and the agent does the rest. ## Step 1: Connect a Lightning wallet The MCP moves real money, so it needs a wallet. Any one of these works — pick the one you already have: - **NWC** (Nostr Wallet Connect) — a connection string from Alby Hub, or any NWC wallet. Free and L402-ready. - **Strike** — an API key. L402-ready (returns the preimage L402 needs). - **LND** — your own node (best L402 guarantees). - **OpenNode** — an API key. Works for direct payments, but **not** for the L402 pay-per-call flow this guide is built on — OpenNode doesn't return preimages, so `access_l402_resource` can't complete. Pick one of the three above for agent-marketplace buying. See [MCP Wallet Setup](./mcp-wallet-setup.md) for the full comparison and per-wallet steps. You'll use the resulting credential (`NWC_CONNECTION_STRING`, `STRIKE_API_KEY`, or the `LND_REST_HOST` + `LND_MACAROON_HEX` pair) in Step 3. :::tip Wallet priority When several wallets are configured the MCP picks in order **LND > NWC > Strike > OpenNode** (optimized for L402 preimage support). Override with `WALLET_PRIORITY`. ::: ## Step 2: Install the MCP server ```bash dotnet tool install -g LightningEnable.Mcp ``` This puts a `lightning-enable-mcp` command on your PATH. (Python alternative for the NWC wallet used below: `uvx --from "lightning-enable-mcp[nwc]" lightning-enable-mcp` — NWC wallets need the `[nwc]` extra.) ## Step 3: Wire it into your Buzz agent A Buzz agent takes its MCP servers per session as an `mcpServers` array — `buzz-agent` spawns each as a stdio subprocess and namespaces its tools as `server__tool` (so you'll see `lightning-enable__discover_agent_services` in traces). **Self-hosted `buzz-agent`** — add this to the session config: ```json { "mcpServers": [ { "name": "lightning-enable", "command": "lightning-enable-mcp", "args": [], "env": [ { "name": "NWC_CONNECTION_STRING", "value": "nostr+walletconnect://..." } ] } ] } ``` Swap the `env` entry for `STRIKE_API_KEY`, or the `LND_REST_HOST` + `LND_MACAROON_HEX` pair, if that's your wallet. :::note Managed agents (Buzz desktop app) Add the MCP server in the agent's settings UI — name `lightning-enable`, command `lightning-enable-mcp`, and your wallet env var. The app writes it into the managed-agent config for you. ::: :::warning Keep the credential out of your repo The wallet key can spend money. Set it via the environment, scope it to this agent, and never commit it. `buzz-agent` only passes through the env you explicitly list — nothing else leaks into the MCP subprocess. ::: ## Step 4: Set a spending limit — before anything can spend Your agent now holds a spending capability. Cap it first. Create `~/.lightning-enable/config.json`: ```json { "currency": "USD", "tiers": { "autoApprove": 0.10, "logAndApprove": 0.50, "formConfirm": 2.00, "urlConfirm": 5.00 }, "limits": { "maxPerPayment": 10.00, "maxPerSession": 25.00 } } ``` Limits are **USD-denominated** (converted to sats at runtime). `limits.maxPerPayment` caps any single payment and `limits.maxPerSession` caps the total across a run — a payment over either is refused and the agent is told to check `get_budget_status`. The `tiers` decide where confirmation kicks in: anything above `autoApprove` needs an out-of-band code printed to the server console, which the agent can't read itself. This file is the source of truth and only you can edit it — an agent can call `configure_budget` to *tighten* the runtime caps but never raise them above these limits. :::warning Budget Configuration Required Treat the budget as the blast radius: if the agent misbehaves or is steered by a malicious listing, this is the most it can spend. Set it before first use. See [AI Spending Security](./ai-spending-security.md). ::: ## Step 5: Prove it works — discover, pay, consume Ask your agent to buy something cheap and real: > Discover an image-upscaling service, then upscale this image. The agent calls `discover_agent_services` to find providers, picks one, and calls `access_l402_resource` on its endpoint — which hits the `HTTP 402`, pays the Lightning invoice from your wallet (within budget), and returns the result. Run `get_payment_history` to see the receipt, or `get_budget_status` to see what's left. Or hand it a card straight from your bridge's Services channel: find one with `@bridge find`, give the agent the `Endpoint:` URL, and let it pay and fetch. ## What your agents can do now One MCP install unlocks the full loop: - **Discover** — `agent_services` with `action="discover"` or `action="reputation"` (formerly `discover_agent_services` / `get_agent_reputation`), plus `discover_api`. Find providers on the marketplace and check their track record before spending. *(wallet only)* - **Buy & consume** — `access_l402_resource` (auto-pay and fetch), `pay_l402_challenge` (manual), `agent_services` with `action="settle"` (pay an agreed L402 endpoint, formerly `settle_agent_service`). *(wallet only)* — kicking off a formal agreement with `agent_services` `action="request"` (formerly `request_agent_service`) needs a producer key (see below). - **Publish** — `agent_services` with `action="publish"` lists your agent's own service (a `kind:38400` on the marketplace); `l402_producer` with `action="create"` gates it behind Lightning; `agent_services` with `action="attest"` records reviews. Once published, it's discoverable by every other agent — including through the NostrWolfe bridge's Services channel. *(producer key — see below)* - **Wallet & budget** — `get_balance`, `pay_invoice`, `create_invoice`, `budget`, and more. *(wallet only)* See the [MCP Complete Guide](./mcp-complete-guide.md) for every tool (including the deprecated alias table, if you're calling any of the old per-operation names above directly). :::note What needs a producer key Discovering, paying, and settling are free — wallet only. Two tools are gated by a `LIGHTNING_ENABLE_API_KEY`: **`l402_producer`** (every action — create, verify, and the receive/proxy/publish actions) and **`agent_services`** for four of its seven actions — `request`, `publish`, `unpublish`, and `attest` (`discover`, `settle`, and `reputation` are free). The fastest way to get a key: have the agent call `create_lightning_enable_account` with your email — it activates an account with a ~100-sat Lightning payment from your wallet and saves the key to `~/.lightning-enable/config.json`, unlocking those tools. You can also paste a key from your [Lightning Enable dashboard](https://api.lightningenable.com/dashboard) into that config file or set it as the `LIGHTNING_ENABLE_API_KEY` env var. ::: :::tip Free forever — no card required That 100-sat activation is **not** a subscription. It starts a 30-day Agentic Commerce trial with no card on file, and if you never upgrade, the account drops to a perpetual **Free Producer Sandbox** — your key keeps working, capped at 3 endpoints, 200 challenges/month, and 1,000 sats per challenge. Consuming services (the wallet side) is never affected by tier. ::: ## How this fits with the NostrWolfe bridge Two independent pieces, one workflow. The **bridge** makes the marketplace *visible* inside your community — discovery, in the room where your agents already work, with no wallet. The **MCP** gives an individual agent the ability to *act* — pay for what it found, or publish its own service. Run the bridge and your agents can see the market; add the MCP and they can transact in it. Neither depends on the other, and neither hands your keys to a vendor. ## Next steps - [MCP Wallet Setup](./mcp-wallet-setup.md) — choose and connect a wallet - [MCP Complete Guide](./mcp-complete-guide.md) — every tool and capability - [AI Spending Security](./ai-spending-security.md) — budgets and safety - [NostrWolfe bridge](https://github.com/refined-element/nostrwolfe-bridge) — mirror the marketplace into your community ============================================================================== # Claude Code Setup Source: https://docs.lightningenable.com/products/agentic-commerce/claude-code-setup ============================================================================== # Give Claude Code a Lightning Wallet Get Claude Code paying Lightning invoices and accessing L402 APIs in under 3 minutes. For the full MCP setup guide (all clients, all wallet options), see **[MCP Quickstart](./mcp-quickstart.md)**. --- ## Step 1: Get a Strike API Key [Strike](https://strike.me) is the fastest way to get started — no infrastructure required. 1. Go to [developer.strike.me](https://developer.strike.me) and log in with your Strike account 2. Click **Generate API Key** and copy the key :::tip Why Strike? Strike requires zero infrastructure, supports L402 (returns preimage), and handles custody. Other options: [NWC wallets](/products/agentic-commerce/mcp-wallet-setup) (CoinOS, Alby Hub, CLINK) or [LND](/products/agentic-commerce/lnd-setup) (self-hosted). ::: --- ## Step 2: Install the MCP Server ```bash # .NET (recommended) dotnet tool install -g LightningEnable.Mcp # Or Python pip install lightning-enable-mcp ``` :::note NWC wallets (Python) The base Python install works on every platform (including Windows). If you connect a **Nostr Wallet Connect (NWC)** wallet, install the optional extra instead: `pip install lightning-enable-mcp[nwc]`. Other wallets (LND, Strike, OpenNode) don't need it. (.NET is unaffected.) ::: --- ## Step 3: Configure Claude Code Register the server with the `claude mcp add` command: **.NET:** ```bash claude mcp add --transport stdio lightning-enable \ --env STRIKE_API_KEY=your-strike-api-key \ -- lightning-enable-mcp ``` **Python:** ```bash claude mcp add --transport stdio lightning-enable \ --env STRIKE_API_KEY=your-strike-api-key \ -- uvx lightning-enable-mcp ``` Alternatively, create a project-scoped `.mcp.json` in your project root (don't commit API keys): ```json { "mcpServers": { "lightning-enable": { "command": "lightning-enable-mcp", "env": { "STRIKE_API_KEY": "your-strike-api-key" } } } } ``` Restart Claude Code after saving, then run `/mcp` to verify the server is connected. :::note Claude Desktop is different Claude Desktop does **not** use `claude mcp add` — it reads `claude_desktop_config.json` (**macOS:** `~/Library/Application Support/Claude/`, **Windows:** `%APPDATA%\Claude\`, **Linux:** `~/.config/claude/`). See the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the Desktop config format. ::: --- ## Step 4: Test It ``` Check my Lightning balance ``` Claude will call `get_balance` and show your Strike balance. Try also: - `Pay this Lightning invoice: lnbc...` - `Fetch data from https://agent-commerce.store/api/weather/forecast?lat=40.71&lon=-74.00` - `Buy me a Lightning Enable t-shirt from store.lightningenable.com` --- ## Spending Limits Create `~/.lightning-enable/config.json` to protect yourself: ```json { "currency": "USD", "tiers": { "autoApprove": 0.10, "logAndApprove": 1.00, "formConfirm": 10.00 }, "limits": { "maxPerPayment": 50.00, "maxPerSession": 20.00 } } ``` --- ## Available Tools The MCP server is open-source (MIT) and free to install. Wallet, invoice, L402, budget, and API-discovery tools work out of the box with just a wallet. Producer tools (sell access via L402) and Agent Service Agreement tools (agent-to-agent commerce over Nostr) unlock with a Lightning Enable API key. See the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the full tool list. --- ## Next Steps - [MCP Quickstart](./mcp-quickstart.md) — Full setup guide for all MCP clients - [Spending Security](/products/agentic-commerce/ai-spending-security) — Advanced budget controls - [Wallet Options](/products/agentic-commerce/mcp-wallet-setup) — NWC, LND, and other wallets - [Agent Commerce Store](https://agent-commerce.store) — L402 APIs to test with ============================================================================== # Cursor Setup Source: https://docs.lightningenable.com/products/agentic-commerce/cursor-setup ============================================================================== # Add Lightning Payments to Cursor Set up the Lightning Enable MCP server in Cursor so your AI agent can pay Lightning invoices and access L402-protected APIs. --- ## Step 1: Get a Strike API Key 1. Create a [Strike](https://strike.me) account 2. Go to [developer.strike.me](https://developer.strike.me) 3. Generate an API key See the [Claude Code guide](/products/agentic-commerce/claude-code-setup#step-1-get-a-strike-api-key) for detailed steps. --- ## Step 2: Install the MCP Server ```bash # .NET dotnet tool install -g LightningEnable.Mcp # Or Python pip install lightning-enable-mcp ``` :::note NWC wallets (Python) The base Python install works on every platform (including Windows). If you connect a **Nostr Wallet Connect (NWC)** wallet, install the optional extra instead: `pip install lightning-enable-mcp[nwc]`. Other wallets (LND, Strike, OpenNode) don't need it. (.NET is unaffected.) ::: --- ## Step 3: Configure Cursor You can configure via the UI or by editing the config file directly. ### Option A: Cursor Settings UI 1. Open **Cursor Settings** (gear icon) 2. Go to **Tools & MCP** 3. Click **New MCP Server** 4. Paste the JSON config below ### Option B: Config File Create or edit `.cursor/mcp.json` in your project root (project-specific) or `~/.cursor/mcp.json` (global): **.NET:** ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "STRIKE_API_KEY": "your-strike-api-key" } } } } ``` **Python:** ```json { "mcpServers": { "lightning-enable": { "command": "uvx", "args": ["lightning-enable-mcp"], "env": { "STRIKE_API_KEY": "your-strike-api-key" } } } } ``` :::warning Don't commit API keys. Use the global config (`~/.cursor/mcp.json`) for credentials, or use environment variables set in your shell profile. ::: Fully restart Cursor after saving (not just close the window). --- ## Step 4: Verify Open **Cursor Settings > Tools & MCP**. You should see `lightning-enable` listed with a green status indicator and the out-of-the-box tools available. Setting `LIGHTNING_ENABLE_API_KEY` additionally unlocks the producer tools and the ASA request/publish/unpublish tools (ASA discovery, settlement, and reputation reads work without a key). Or ask in Cursor's Agent mode: ``` What MCP tools do you have available? ``` ### Test a Payment ``` Check my Lightning wallet balance ``` ### Access an L402 API ``` Get weather data for New York from agent-commerce.store ``` --- ## Project vs Global Config | Location | Scope | Best For | |----------|-------|---------| | `.cursor/mcp.json` | This project only | Sharing tool config with team (no secrets!) | | `~/.cursor/mcp.json` | All projects | API keys, personal tools | When both exist, project-level takes priority. --- ## Differences from Claude Desktop The config format is nearly identical. The only differences: | | Cursor | Claude Desktop | |---|---|---| | Config file | `.cursor/mcp.json` or `~/.cursor/mcp.json` | `claude_desktop_config.json` | | Root key | `"mcpServers"` (same) | `"mcpServers"` (same) | | UI config | Settings > Tools & MCP | File edit only | | Remote servers | Supported (SSE + HTTP) | stdio only | --- ## Next Steps - [Spending Security](/products/agentic-commerce/ai-spending-security) — Budget controls - [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) — full tool reference - [Agent Commerce Store](https://agent-commerce.store) — L402 APIs to test with ============================================================================== # Dashboard Guide Source: https://docs.lightningenable.com/products/agentic-commerce/dashboard-guide ============================================================================== # Dashboard Guide Manage L402 proxies, monitor revenue, configure pricing — all from the dashboard, no code required for the dashboard surface itself. :::tip Zero Infrastructure Required The dashboard is your complete control panel for L402 proxy management. No Lightning node to run, no server to deploy, no DevOps expertise needed. Configure everything from your browser. ::: ## Accessing the Dashboard 1. Navigate to [api.lightningenable.com/dashboard](https://api.lightningenable.com/dashboard) 2. Enter your email for a magic link (or use your API key directly) 3. You're in — no additional accounts or passwords needed ![Dashboard Home](/img/dashboard/dashboard-home.png) ### Configuring Your Payment Provider Before creating proxies, add your payment provider credentials in **Settings → Payment Provider**: 1. Go to the **Settings** page in the dashboard 2. In the **Payment Provider** section, pick your provider from the **Settlement Provider** dropdown — **Strike (Recommended)** or **OpenNode** 3. Paste your API key in the field that appears (labeled **Strike API Key** or **OpenNode API Key** depending on the dropdown selection) 4. Click **Save Key**, then **Validate** to confirm the key is working :::tip Get a Strike API Key Sign up at [strike.me](https://strike.me) and get your API key from [dashboard.strike.me](https://dashboard.strike.me). Strike is the recommended provider — it supports preimage return for full L402 compatibility. ::: :::tip Start with the OpenNode Dev Environment If you choose OpenNode and don't have an account yet, sign up at [app.dev.opennode.com](https://app.dev.opennode.com) — no KYB required, test with Bitcoin testnet. When you're ready for production, swap your dev key for a production key in Settings. ::: ## Dashboard Overview The home screen shows: - **4 summary cards** — Total Proxies, Active Proxies, Total Requests, and Total Revenue (cumulative across all your proxies) - **Quick Actions** — Create Proxy and shortcuts to common tasks - **Recent Proxies** — Your most recently modified proxies with status indicators ## Managing Proxies ### Proxy List Navigate to the **Proxies** tab to see all your L402 proxies in a searchable, sortable table. ![Proxy List](/img/dashboard/proxy-list.png) Key features: - **Search** matches against proxy name, Proxy ID slug, and Target URL — type any substring of any of the three - **Sort** columns by Name, Price (sats), Requests, or Revenue - **Enable/disable** proxies with one-click toggle switches - **Status indicators** — green (active), gray (disabled) ### Creating a Proxy (4-Step Wizard) Click **Create Proxy** to launch the guided wizard. **Step 1: Basic Info** Enter a name and optional description for your proxy. ![Create Proxy - Step 1](/img/dashboard/proxy-create-step1.png) **Step 2: Target URL** Configure the target API URL — the base URL of the API you are proxying. All requests to `/l402/proxy/{proxy-id}/*` are forwarded to this address. Lightning Enable does not store or inject upstream credentials; see the [proxy setup walkthrough](/products/agentic-commerce/proxy-setup-walkthrough#handling-apis-that-require-authentication) for how to handle APIs that require auth. ![Create Proxy - Step 2](/img/dashboard/proxy-create-step2.png) **Step 3: Pricing** Set the default price in satoshis per request and the token validity period — how long a paid token stays usable before the client must pay again. An approximate USD equivalent is displayed next to the sats price; it uses a fixed reference conversion rate and is an **estimate**, not a live market price. :::note Per-proxy token validity was fixed in the July 2026 update; proxies created earlier always issued tokens with the 1-hour default regardless of the value entered here. ::: ![Create Proxy - Step 3](/img/dashboard/proxy-create-step3.png) **Step 4: Review** Review all settings before creating the proxy. ![Create Proxy - Step 4](/img/dashboard/proxy-create-step4.png) ## Proxy Detail Page Click any proxy to view its detail page with multiple tabs. :::tip Full walkthrough available For a step-by-step guide to the entire setup flow — from creating a proxy to getting discovered by AI agents — see [Setting Up Your Proxy](/products/agentic-commerce/proxy-setup-walkthrough). ::: ### Overview Tab ![Proxy Detail - Overview](/img/dashboard/proxy-detail-overview.png) Shows: - Proxy information (name, target URL, status) - Quick stats (Total Requests and Total Revenue — lifetime totals for this proxy) - **Integration URL** — the full proxy URL to share with clients - **Example curl command** — copy-paste ready for testing ### Pricing Tab ![Proxy Detail - Pricing](/img/dashboard/proxy-detail-pricing.png) The Pricing tab combines everything needed to make your API discoverable and correctly priced: - **Default Fallback Price** — the price charged for any request that does not match a specific endpoint rule. Set this once if all your endpoints cost the same amount. - **Manifest settings** — turn on the manifest to publish a public description file that AI agents read. Fill in the Service Description, Contact Email, Documentation URL, and Terms of Service URL to complete your public listing. - **API Endpoints table** — the per-endpoint catalog that agents read. Use **Scan API** to import from your OpenAPI/Swagger spec, or **Add Manual** for APIs without a spec. - **Preview & Share** — URLs for the machine-readable manifest (JSON), a human-readable version (Markdown), and the public registry. See the [full Pricing tab walkthrough](/products/agentic-commerce/proxy-setup-walkthrough#step-3--configure-the-manifest) for detailed guidance on each section. :::info Coming soon Per-day analytics charts and a real-time request log are on the roadmap. Aggregate Total Requests and Total Revenue are visible on the **Overview** tab today; daily breakdowns and request-level logs will surface as separate tabs once they're backed by real telemetry. Until then, query `GET /api/proxy/{proxyId}/analytics` for the totals via REST. ::: ## Dashboard vs API Both the dashboard and REST API provide full proxy management. Use whichever fits your workflow: | Action | Dashboard | REST API | |--------|-----------|----------| | Create proxy | 4-step visual wizard | `POST /api/proxy` | | Update pricing | Inline editing | `PUT /api/proxy/{proxyId}` | | Toggle status | One-click switch | `PUT /api/proxy/{proxyId}` | | Endpoint pricing | Visual table | `POST /api/proxy/{proxyId}/pricing` | | Test connectivity | (API-only today) | `POST /api/proxy/{proxyId}/test` | | View totals | Overview tab numbers | `GET /api/proxy/{proxyId}/analytics` | :::info REST API routes use `{proxyId}` — the URL-safe slug (e.g., `openweather-current-conditions-a1b2c3d4`), not the dashboard's internal numeric database row ID. The dashboard and the REST API operate on the same underlying data model (same database tables, same validation rules), but the dashboard talks to the database through internal services rather than calling its own HTTP endpoints — so anything the dashboard can do is achievable programmatically through the REST API, just not via the exact same call sequence. ::: ## Next Steps - [Proxy Configuration](/products/agentic-commerce/proxy-configuration) - Full API reference for proxy management - [API Monetization](/products/agentic-commerce/api-monetization) - Strategies for monetizing APIs - [How It Works](/products/agentic-commerce/how-it-works) - Technical deep dive into L402 ============================================================================== # Run L402 Anywhere: Hermes + NWC Source: https://docs.lightningenable.com/products/agentic-commerce/hermes-nwc-setup ============================================================================== # Run L402 Anywhere: Hermes + NWC Give an AI agent a tiny Lightning wallet and the Lightning Enable MCP server, and it can pay [L402](https://github.com/lightninglabs/L402)-protected APIs anywhere on the internet — no card, no account, no hosted infrastructure. With [Hermes](https://hermes-agent.nousresearch.com) as the host, the same setup reaches you on your **phone** over Telegram, Signal, WhatsApp, and other chat apps. :::tip Why this setup Most "agent pays for things" demos have a deployment problem: the agent needs a wallet **and** a host before it can pay for anything. Hermes runs the MCP locally and bridges to your phone, so you get mobile agent payments **without** a hosted MCP to run or trust. Your wallet stays yours; Lightning Enable never holds funds. ::: ## The beginner stack | Layer | Pick | Why | |---|---|---| | Agent host | **Hermes** | Local MCP client with Telegram/Signal/WhatsApp gateways — mobile access, no hosted layer. | | Payment tools | **Lightning Enable MCP** | Gives the agent `access_l402_resource`, `pay_l402_challenge`, wallet + budget tools. | | Wallet | **Alby Hub** via NWC | Alby-hosted wallet (Alby Cloud) — no infrastructure of your own to run or trust, and returns the Lightning **preimage** that L402 requires. | | Budget | A small funded wallet | The wallet balance is the hard leash; software limits are defense-in-depth. | :::caution Wallet must return a preimage L402 verification needs the payment **preimage**. NWC wallets (CoinOS, Alby Hub, CLINK), LND, and Strike return it. **OpenNode does not** and can't be used to *pay* L402 resources. ::: ## Prerequisites - macOS, Linux, Windows, or WSL - The .NET 9 runtime (for the .NET MCP tool) **or** Python 3.10+ (for the Python one) - [Hermes](https://hermes-agent.nousresearch.com) installed - An Alby Hub account (via [Alby Cloud](https://albyhub.com)) with a small amount of sats (1,000–10,000 is plenty to start) ## Step 1 — Install Hermes ```bash curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash ``` Run the setup wizard (pick your model/provider), then confirm it's healthy: ```bash hermes setup hermes doctor ``` ## Step 2 — Install the Lightning Enable MCP ```bash # .NET (recommended) dotnet tool install -g LightningEnable.Mcp # Or Python — for an NWC wallet, install the optional extra: pip install "lightning-enable-mcp[nwc]" ``` Confirm the command is on your PATH: ```bash command -v lightning-enable-mcp ``` :::note NWC on Python `pip install lightning-enable-mcp` works on every platform, but **NWC wallets need the extra**: `pip install "lightning-enable-mcp[nwc]"`. The .NET tool needs no extra. ::: ## Step 3 — Create an Alby Hub NWC connection 1. Go to [Alby Hub](https://albyhub.com) and choose the **Alby Cloud** hosted option — an Alby-hosted Lightning node, so there's no infrastructure of your own to run or trust. (Already running your own node? Alby Hub can also be self-hosted; Alby Cloud is just the fastest path for this guide.) 2. Fund it with a small amount (e.g. 1,000–10,000 sats). 3. Open **Connections** and create a new **app connection** for your agent. 4. Grant the permissions an L402-paying agent needs: - `pay_invoice` - `get_balance` - (optional) `make_invoice` if the agent should also *receive* payments 5. Set a **monthly budget** for the connection. Alby Hub enforces a real per-connection spend cap here — a genuine advantage over CoinOS, which doesn't offer one. 6. Copy the `nostr+walletconnect://…` connection string. :::warning Treat the NWC string like a wallet password Anyone with the connection string can spend within its permissions and limits. Keep it out of chat, screenshots, and shared configs. ::: ## Step 4 — Configure the wallet + budget Create `~/.lightning-enable/config.json`: ```json { "currency": "USD", "tiers": { "autoApprove": 0.10, "logAndApprove": 1.00, "formConfirm": 10.00 }, "limits": { "maxPerPayment": 5.00, "maxPerSession": 20.00 }, "wallets": { "nwcConnectionString": "nostr+walletconnect://PASTE_YOUR_ALBY_HUB_STRING_HERE", "priority": "nwc" } } ``` Lock the file down (NWC strings are sensitive): ```bash chmod 600 ~/.lightning-enable/config.json ``` | Threshold | Behavior | |---|---| | Under $0.10 | Auto-pay silently | | $0.10 – $1.00 | Pay and log | | $1.00 – $5.00 | Confirmation required (server prints a code to its console for you) | | Over $5/payment or $20/session | Denied | :::note Config file vs. environment variable NWC strings contain characters that are easy to break in shell profiles and GUI launchers, so the config file is the reliable path. (If you prefer env vars, the equivalents are `NWC_CONNECTION_STRING` and `WALLET_PRIORITY=nwc`.) ::: ## Step 5 — Add the MCP to Hermes Because the wallet lives in the config file, adding the server is one line: ```bash hermes mcp add lightning-enable --command lightning-enable-mcp hermes mcp test lightning-enable ``` Expected: ```text ✓ Connected ✓ Tools discovered ``` ## Step 6 — Verify for 1 sat This is the moment of truth — a real end-to-end L402 payment against the public **1-sat test endpoint**. It proves your wallet, the preimage flow, and L402 all work, for one satoshi. First, check the agent can see the wallet: ```text Check the Lightning Enable wallet balance. ``` Then pay the test endpoint: ```text Access this L402 resource: https://api.lightningenable.com/l402/test/ping ``` What happens under the hood: 1. The agent requests the URL and gets `402 Payment Required` with a 1-sat Lightning invoice + macaroon. 2. The MCP pays the invoice through your Alby Hub NWC wallet. 3. Alby Hub returns the preimage. 4. The MCP retries with `Authorization: L402 :`. 5. The endpoint returns `200 OK`. If you get the `200`, your whole stack works. (Prefer the command line? The same endpoint responds to `curl https://api.lightningenable.com/l402/test/ping` with the 402 challenge so you can inspect the invoice before paying.) ## Step 7 — Take it to your phone Point Hermes' messaging gateway at Telegram (or Signal, WhatsApp, etc.) and talk to your agent from anywhere: ```bash hermes gateway setup hermes gateway run ``` Now you can message your agent "pay the 1-sat test endpoint" or "buy the premium forecast from this API" from your phone, and it pays over Lightning from the bounded wallet — no hosted MCP required. ## What the agent can do next Once it can pay L402, the same agent handles real tasks. Pair this setup with the open-source [**pay-l402-anywhere** skill](https://github.com/refined-element/lightning-enable-skills/tree/main/skills/pay-l402-anywhere) (it works in Hermes, Claude Code, and Cursor unmodified) — discover a paid API, check what you can afford, confirm, pay, and report the sats spent. Key consumer tools the MCP exposes: - `access_l402_resource` — fetch a URL and auto-pay the L402 challenge - `pay_l402_challenge` — pay a challenge you already hold - `discover_api` — search the L402 registry or fetch an API's manifest - `get_balance` / `budget` (with `action="status"`) — know your funds and limits - `verify_confirmation_code` — check what a console confirmation code authorizes (verification only; to approve an over-threshold payment, re-call the original payment tool with `confirmation_nonce=`) ## Security posture The safe design isn't "trust the agent" — it's **bound what it can spend**: 1. Use a **dedicated small wallet** for the agent. 2. Fund only what it's allowed to spend — the balance is the real ceiling. 3. Set an Alby Hub per-connection monthly budget — a real spend cap Alby Hub enforces on that connection alone. 4. Keep the MCP budget caps as defense-in-depth. 5. Keep `~/.lightning-enable/config.json` at `600`. 6. Rotate the NWC connection if it leaks or you're done with it. A 5,000-sat wallet cannot spend 50,000 sats, no matter what any prompt says. ## Next steps - [Wallet Configuration](/products/agentic-commerce/mcp-wallet-setup) — other NWC wallets (CoinOS, CLINK) and LND - [AI Spending Security](/products/agentic-commerce/ai-spending-security) — how the budget tiers and confirmation gate work - [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) — full tool reference ============================================================================== # How It Works Source: https://docs.lightningenable.com/products/agentic-commerce/how-it-works ============================================================================== # How L402 Works This guide explains the technical details of the L402 protocol implementation in Lightning Enable. ## Protocol Flow ``` ┌──────────┐ ┌─────────────────┐ ┌─────────────┐ │ Client │ │ Lightning Enable│ │ Provider │ └────┬─────┘ └────────┬────────┘ └──────┬──────┘ │ │ │ │ 1. GET /api/premium/data │ │ │──────────────────────────────────>│ │ │ │ │ │ │ 2. Create Lightning Invoice │ │ │───────────────────────────────────>│ │ │ │ │ │ 3. Invoice + Payment Hash │ │ │<───────────────────────────────────│ │ │ │ │ 4. HTTP 402 Payment Required │ │ │ WWW-Authenticate: L402 │ │ │ macaroon="...", invoice="..." │ │ │<──────────────────────────────────│ │ │ │ │ │ 5. Pay invoice (via any wallet) │ │ │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─>│ │ │ │ │ 6. Preimage (proof of payment) │ │ │<─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│ │ │ │ │ 7. GET /api/premium/data │ │ │ Authorization: L402 mac:preim │ │ │──────────────────────────────────>│ │ │ │ │ │ │ 8. Verify SHA256(preimage)==hash │ │ │ Verify macaroon signature │ │ │ │ │ 9. HTTP 200 OK (response) │ │ │<──────────────────────────────────│ │ ``` *Provider* is the merchant's configured payment provider — **Strike** (the default) or **OpenNode**. Lightning Enable talks to whichever one the merchant selected; the L402 flow is identical either way. Lightning Enable does not hold funds — the payment provider facilitates custody and settlement. ## Key Concepts ### 1. Lightning Invoice (BOLT11) When a client requests a protected endpoint, Lightning Enable creates a Lightning invoice: ``` lnbc100n1pnxyzabc... (encoded invoice) ``` The invoice contains: - **Amount** in satoshis - **Payment hash** (SHA256 of a secret preimage) - **Expiry time** - **Destination** (the payment provider's node — Strike or OpenNode, per merchant configuration) ### 2. Payment Hash & Preimage The payment hash is the key to L402: ``` preimage (32 bytes, secret) → SHA256 → payment_hash (32 bytes, public) ``` - The **payment hash** is included in the invoice - The **preimage** is revealed when the invoice is paid - Knowing the preimage proves payment was made ### 3. Macaroon A macaroon is a cryptographic bearer token signed with HMAC-SHA256. Lightning Enable embeds a set of **caveats** (restrictions) into every macaroon at issuance time. These caveats bind the token to the exact context it was created for: ```json { "identifier": "lightning-enable:payment_hash:expires", "caveats": [ "services = lightning-enable:0", "path = /api/premium/data", "merchant_id = 42", "charge_id = abc123-def456", "amount_sats = 100", "expires = 1704067200" ], "signature": "hmac-sha256-signature" } ``` Each caveat enforces a specific security constraint: | Caveat | Purpose | |--------|---------| | `path` | Binds the token to the API path it was issued for. Supports exact match or wildcard prefix (e.g., `/l402/proxy/my-api/*`). | | `merchant_id` | Binds the token to the issuing merchant, preventing cross-tenant token reuse. | | `amount_sats` | Binds the token to the price at issuance, preventing reuse at a different price tier. | | `expires` | Sets the token expiration as a Unix timestamp (default: 1 hour). | | `charge_id` | Records the payment provider's charge ID (Strike or OpenNode) for the associated payment. | | `services` | Identifies the service name and tier. | All caveats are verified on every request. Any unrecognized caveat causes verification to fail (closed-world assumption), ensuring forward compatibility and defense in depth. ### 4. L402 Credential The client combines macaroon and preimage: ``` Authorization: L402 : ``` ## Verification Process When Lightning Enable receives an L402 credential: ### Step 1: Parse Credential ```javascript const [scheme, credential] = authHeader.split(' '); const [macaroon, preimage] = credential.split(':'); ``` ### Step 2: Verify Preimage ```javascript // Extract payment hash from macaroon const paymentHash = extractPaymentHash(macaroon); // Compute hash of preimage const computedHash = sha256(hexToBytes(preimage)); // Verify match if (computedHash !== paymentHash) { throw new Error('Preimage does not match payment hash'); } ``` ### Step 3: Verify Macaroon Signature ```javascript // Verify macaroon wasn't tampered with const isValid = verifyMacaroonSignature(macaroon, rootKey); if (!isValid) { throw new Error('Invalid macaroon signature'); } ``` ### Step 4: Check Caveats ```javascript // Verify all caveats are satisfied const caveats = extractCaveats(macaroon); // Check expiration if (caveats.expires < Date.now()) { throw new Error('Token expired'); } // Check path binding if (!pathMatches(requestPath, caveats.path)) { throw new Error('Token not valid for this path'); } // Check merchant isolation if (caveats.merchant_id !== requestMerchantId) { throw new Error('Token not valid for this merchant'); } // Check price tier if (caveats.amount_sats !== endpointPriceSats) { throw new Error('Token amount mismatch'); } ``` ## Payment Hash Extraction Lightning Enable extracts the payment hash directly from BOLT11 invoices: ```csharp private byte[]? ExtractPaymentHashFromBolt11(string invoice) { // Find the '1' separator between human-readable and data parts var separatorIndex = invoice.LastIndexOf('1'); var dataPart = invoice.Substring(separatorIndex + 1); // Skip timestamp (first 7 chars) dataPart = dataPart.Substring(7); // Find tagged field 'p' (payment hash) // Tag 'p' = 1, followed by data length, followed by 52 bech32 chars // 52 bech32 chars * 5 bits = 260 bits = 256 bits (32 bytes) + padding var paymentHash = ParseTaggedField(dataPart, 'p'); return paymentHash; // 32 bytes } ``` ## Token Caching For performance, verified tokens are cached: ```csharp public class L402TokenCache { private readonly IMemoryCache _cache; private readonly TimeSpan _cacheDuration = TimeSpan.FromMinutes(5); public bool TryGetVerified(string preimage, out L402Token token) { return _cache.TryGetValue(preimage, out token); } public void CacheVerified(string preimage, L402Token token) { _cache.Set(preimage, token, _cacheDuration); } } ``` ## Multi-Use Tokens A single L402 payment can be used for multiple requests during the token validity period: 1. Client pays once 2. Receives preimage 3. Uses same macaroon:preimage for subsequent requests 4. Token valid until expiration — configurable per proxy in the dashboard wizard, falling back to the global 1-hour default :::note Per-proxy token validity was fixed in the July 2026 update; earlier tokens always used the 1-hour default. ::: ## Security Considerations ### Caveat-Based Token Binding Macaroon caveats are the primary defense against token misuse. Lightning Enable enforces caveats that prevent three categories of attack: **Path binding** (`path` caveat) -- A token issued for `/api/premium/v1` cannot be used to access `/api/premium/v2`. This prevents clients from paying for a cheap endpoint and reusing the token against an expensive one. Wildcard paths (e.g., `/l402/proxy/my-api/*`) allow sub-path access when appropriate. **Merchant isolation** (`merchant_id` caveat) -- In Lightning Enable's multi-tenant architecture, each merchant operates independently. The `merchant_id` caveat prevents a token issued by Merchant A from being replayed against Merchant B's endpoints. This is enforced bidirectionally: if a request carries a merchant context, the token must contain a matching `merchant_id`, and if a token contains a `merchant_id`, the request must have a matching merchant context. **Price tier enforcement** (`amount_sats` caveat) -- A token purchased at 10 sats for a demo endpoint cannot be reused against a 100-sat premium endpoint, even if both endpoints share a wildcard path pattern. The server compares the token's `amount_sats` caveat against the current endpoint's configured price and rejects mismatches. **Unknown caveat rejection** -- Any caveat the server does not recognize causes verification to fail. This closed-world approach ensures that if new caveat types are added in the future, older verification logic will not silently skip them. ### Preimage Security - Treat preimages like passwords - Don't log full preimages - Use HTTPS to prevent interception ### Macaroon Tampering - Macaroons are signed with HMAC-SHA256 - Root key must be kept secret (`L402_ROOT_KEY` environment variable) - Any modification invalidates the signature ### Token Expiration - Configure appropriate validity periods - Shorter = more secure, but more payments needed - Longer = better UX, but higher risk if compromised ### Rate Limiting Even with valid payments, implement rate limiting: ```csharp // Limit requests per payment hash services.AddRateLimiter(options => { options.AddPolicy("L402", httpContext => { var paymentHash = GetPaymentHash(httpContext); return RateLimitPartition.GetFixedWindowLimiter( paymentHash, _ => new FixedWindowRateLimiterOptions { PermitLimit = 100, Window = TimeSpan.FromHours(1) }); }); }); ``` ## Configuration :::info Internal server configuration — shown for protocol understanding The JSON blocks below (`L402` options: `ProtectedPaths`, `EndpointPricing`, etc.) are **Lightning Enable's own server-side settings** — they configure the hosted service itself, and merchants cannot set them. They're shown here so you can see how the protocol implementation is driven. As a merchant, you control per-proxy pricing, endpoint pricing rules, and token validity through the [dashboard](/products/agentic-commerce/dashboard-guide) or the [proxy management REST API](/products/agentic-commerce/proxy-configuration). (If you self-host L402 via the [native middleware](/products/agentic-commerce/native-integration), your own app uses equivalent settings.) ::: ### L402 Settings ```json { "L402": { "Enabled": true, "ServiceName": "my-api", "DefaultPriceSats": 100, "DefaultTokenValiditySeconds": 3600, "InvoiceExpirySeconds": 600, "CacheVerifiedTokens": true, "TokenCacheSeconds": 300 } } ``` ### Protected Paths ```json { "L402": { "ProtectedPaths": [ "/api/premium/*", "/api/ai/*" ], "ExcludedPaths": [ "/api/public/*", "/health" ] } } ``` ### Endpoint Pricing ```json { "L402": { "EndpointPricing": [ { "PathPattern": "/api/ai/gpt4", "PriceSats": 500 }, { "PathPattern": "/api/ai/dalle", "PriceSats": 1000 }, { "PathPattern": "/api/premium/*", "PriceSats": 50 } ] } } ``` ## Error Responses ### 402 Payment Required (initial challenge) ```json { "error": "Payment Required", "message": "Pay the Lightning invoice to access this resource", "l402": { "macaroon": "AgEL...", "invoice": "lnbc100n1p3...", "amount_sats": 100, "payment_hash": "abc123...", "expires_at": "2026-07-03T13:00:00Z" } } ``` ### Failed verification → a fresh 402, never 401/403 When a presented L402 credential fails verification — malformed credential, preimage/payment-hash mismatch, invalid macaroon signature, expired token, or a caveat violation (wrong path, merchant, or price tier) — Lightning Enable does **not** return `401 Unauthorized` or `403 Forbidden`. It **re-issues a fresh 402 challenge** with a new invoice and macaroon. The failure reason is carried in: - the **`X-L402-Error` response header**, and - the body's **`message`** field. ``` HTTP/1.1 402 Payment Required WWW-Authenticate: L402 macaroon="AgEL...", invoice="lnbc100n1p3..." X-L402-Error: Macaroon has expired ``` ```json { "error": "Payment Required", "message": "Macaroon has expired", "l402": { "macaroon": "AgEL... (new macaroon)", "invoice": "lnbc100n1p3... (new invoice)", "amount_sats": 100, "payment_hash": "def456...", "expires_at": "2026-07-03T13:10:00Z" } } ``` Clients should treat **every** 402 as a (re-)challenge: check `X-L402-Error` to learn why the previous credential was rejected, then pay the new invoice from the current response. Code that branches on 401/403 for L402 failures will never execute those branches. ## Next Steps - [API Monetization](/products/agentic-commerce/api-monetization) - Protect your endpoints - [Proxy Configuration](/products/agentic-commerce/proxy-configuration) - Monetize any API - [API Reference](/api-reference/l402) - Complete L402 API docs ============================================================================== # L402 Producer API: Agents That Earn Source: https://docs.lightningenable.com/products/agentic-commerce/l402-producer-api ============================================================================== # L402 Producer API: Agents That Earn :::tip The Key Differentiator Until now, AI agents could only **spend** money. With the L402 Producer API, your agents can **earn** money too. This is true agentic commerce — agents that both buy and sell services autonomously. ::: The L402 Producer API lets Agentic Commerce subscribers turn their AI agents into service providers. Instead of just consuming paid APIs, your agent can **create L402 payment challenges** and **verify payments** before granting access to its own capabilities. ## The Two Sides of Agentic Commerce | Side | What It Does | Who Pays | MCP Tools | |------|-------------|----------|-----------| | **Consumer** (existing) | Agent accesses paid APIs | Your agent pays | `access_l402_resource`, `pay_l402_challenge` | | **Producer** (new) | Agent charges for its services | Other agents pay you | `l402_producer` with `action="create"` / `action="verify"` (formerly the separate `create_l402_challenge` / `verify_l402_payment` tools — see [Deprecated aliases](/products/agentic-commerce/mcp-complete-guide#deprecated-aliases)) | Both sides use the same L402 protocol. The difference is direction: consumers pay invoices, producers create them. ## Prerequisites - **Agentic Commerce subscription** — Agentic Commerce ($49/mo) or Business ([contact us](mailto:support@lightningenable.com)) - **`LIGHTNING_ENABLE_API_KEY`** environment variable set to your merchant API key - A configured payment provider (Strike or OpenNode) on your Lightning Enable account ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "STRIKE_API_KEY": "your-strike-api-key", "LIGHTNING_ENABLE_API_KEY": "your-merchant-api-key" } } } } ``` :::info Consumer Tools Work Out of the Box The out-of-the-box tools (`access_l402_resource`, `pay_l402_challenge`, `pay_invoice`, etc.) require **no API key** — just a wallet. `l402_producer` (every action) and four `agent_services` actions (`request`, `publish`, `unpublish`, `attest`) require a Lightning Enable API key via `LIGHTNING_ENABLE_API_KEY`; the other `agent_services` actions (`discover`, `settle`, `reputation`) work with just a wallet. See the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the full tool list. ::: ## How It Works The full agent-to-agent commerce flow: ``` ┌──────────────────┐ ┌──────────────────┐ │ Requesting Agent │ │ Producer Agent │ │ (Consumer) │ │ (Your Agent) │ └────────┬─────────┘ └────────┬─────────┘ │ │ │ 1. "I need weather data" │ │──────────────────────────────────────> │ │ │ │ │ 2. create_l402_challenge( │ │ resource="/api/weather", │ │ priceSats=50, │ │ description="7-day forecast" │ │ ) │ │ │ 3. HTTP 402 Payment Required │ │ + Lightning invoice + macaroon │ │ <──────────────────────────────────────│ │ │ │ 4. pay_l402_challenge(invoice, mac) │ │ ─ ─ ─ ─ ─ ─ ─ (pays invoice) ─ ─ ─ >│ │ │ │ 5. "Here's my L402 token: │ │ macaroon:preimage" │ │──────────────────────────────────────> │ │ │ │ │ 6. verify_l402_payment( │ │ macaroon, preimage │ │ ) │ │ │ 7. Access granted + response data │ │ <──────────────────────────────────────│ ``` *(Steps 2 and 6 are shorthand for `l402_producer` with `action="create"` and `action="verify"` — see below.)* ## MCP Tools :::note Now one consolidated tool `create_l402_challenge` and `verify_l402_payment` are now `l402_producer` with `action="create"` / `action="verify"` — one tool, selected by action, alongside the newer `configure_receive`/`status`/`create_proxy`/`add_endpoint`/`publish`/`list_challenges` actions covered in [Sell With Your Agent](/getting-started/sell-with-your-agent) and the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide). The old tool names still work as deprecated aliases under `LIGHTNING_ENABLE_TOOL_PROFILE=full`, removed in v3.0.0 — the examples below use the current call form. ::: ### l402_producer with action="create" Create an L402 payment challenge to charge another agent or user for accessing a resource. Formerly the standalone `create_l402_challenge` tool. **Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `resource` | string | Yes | Resource identifier — URL, service name, or description of what you're charging for | | `priceSats` | long | Yes | Price in satoshis to charge | | `description` | string | No | Description shown on the Lightning invoice | **Example:** ``` User: When another agent asks for weather data, charge them 50 sats. Claude: [Uses l402_producer with action="create", resource="/api/weather/forecast", priceSats=50, description="7-day weather forecast"] L402 challenge created! - Invoice: lnbc500n1p3xyza... - Price: 50 sats - Resource: /api/weather/forecast Share the invoice and macaroon with the requesting agent. After they pay, they'll send you an L402 token (macaroon:preimage). Use l402_producer with action="verify" to confirm payment before granting access. ``` **Response:** ```json { "success": true, "challenge": { "invoice": "lnbc500n1p3xyza...", "macaroon": "AgELbGlnaHRuaW5n...", "paymentHash": "abc123def456...", "expiresAt": "2026-03-13T14:30:00Z" }, "resource": "/api/weather/forecast", "priceSats": 50, "instructions": { "forPayer": "Pay the Lightning invoice, then present the L402 token...", "tokenFormat": "L402 {macaroon}:{preimage}", "verifyWith": "After receiving the L402 token from the payer, use l402_producer with action=\"verify\" to confirm payment before granting access." }, "message": "L402 challenge created for 50 sats. Share the invoice with the payer." } ``` --- ### l402_producer with action="verify" Verify an L402 token (macaroon + preimage) to confirm payment was made. Use this after a payer presents an L402 token, before granting access to the resource. Formerly the standalone `verify_l402_payment` tool. **Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `macaroon` | string | Yes | Base64-encoded macaroon from the L402 token | | `preimage` | string | Yes | Hex-encoded preimage (proof of payment) | :::caution Compare the returned resource yourself This tool sends only the macaroon and preimage to the verify endpoint. The API always enforces merchant binding and expiry, but it enforces the **resource** caveat only when the verify request includes a `resource` value — which this tool does not send. `valid: true` therefore means "a real payment for *some* resource of yours" — check that the `resource` field in the result matches the resource the payer is asking for before granting access. See [Token Binding](#token-binding) below. ::: **Example:** ``` User: The requesting agent sent this L402 token. Verify it. Claude: [Uses l402_producer with action="verify", macaroon="AgEL...", preimage="7f8a9b..."] Payment verified! The agent has paid 50 sats for /api/weather/forecast. Granting access now. ``` **Response (valid):** ```json { "success": true, "valid": true, "resource": "/api/weather/forecast", "message": "Payment verified. The payer has paid — you can now grant access to the resource." } ``` **Response (invalid):** ```json { "success": true, "valid": false, "message": "Payment verification failed. The token is invalid or the invoice has not been paid. Do NOT grant access." } ``` ## Challenge Idempotency Send an `Idempotency-Key` header and a retry gets the **same invoice and macaroon** back for the life of that invoice — never a second one your payer could also pay. The key is recorded on the challenge itself, so the replay survives a deploy, a restart, and a load balancer routing your retry elsewhere. ```bash curl -X POST https://api.lightningenable.com/api/l402/challenges \ -H "X-API-Key: YOUR_MERCHANT_API_KEY" \ -H "Idempotency-Key: forecast-req-7f2a" \ -H "Content-Type: application/json" \ -d '{"resource": "/api/weather/forecast", "priceSats": 50}' ``` - Same key with the same resource and price replays the original, and the response carries `X-Idempotency-Replayed: true`. - Same key with a **different** resource or price, while the original invoice is still live, is a `409` — one key means one live charge. - Once that invoice expires (10 minutes by default, `L402Options.InvoiceExpirySeconds`), the key is released and the next call mints fresh — at any price. A spent key is not poisoned. - Need a second challenge for the same resource before then? Use a different key. Without a key, the API falls back to deduplicating on the client's IP address for the same window — fine for middleware on one server, unreliable behind a load balancer. `X-Idempotency-Key`, the spelling this API shipped with, still works and behaves identically. Full rules in the [Producer API Reference](./producer-api-reference#idempotency). ## Knowing When You Got Paid Two ways, and you will usually want both. **Poll** — `GET /api/l402/challenges` lists what you have minted with a `status` of `paid`, `unpaid`, or `expired`: ```bash curl "https://api.lightningenable.com/api/l402/challenges?status=paid&limit=50" \ -H "X-API-Key: YOUR_MERCHANT_API_KEY" ``` **Get notified** — configure a callback URL under **Dashboard → Settings → Webhooks** and Lightning Enable POSTs `l402.challenge.paid` the first time a credential from one of your challenges verifies, HMAC-signed like every other Lightning Enable webhook: ```json { "event": "l402.challenge.paid", "paymentHash": "abc123...", "resource": "/api/weather/forecast", "amountSats": 50, "paidAt": "2026-09-05T18:00:41Z", "idempotencyKey": "forecast-req-7f2a" } ``` It fires **once per challenge** — L402 tokens verify many times inside their window, and only the first transition notifies. It is proof of payment, not a settlement record: the sats settled with your payment provider when the invoice was paid, and Lightning Enable does not hold funds. An invoice paid but never presented back to Lightning Enable produces no event, which is what the `status=unpaid` listing is for. See [Payment webhooks](./producer-api-reference#payment-webhooks). ## REST API Quick Start The MCP tools call two REST endpoints under the hood. You can call them directly from any language. :::info Single source of truth The **[Producer API Reference](./producer-api-reference)** is the authoritative page for these endpoints — full request/response contracts (including the `mppChallenge` field), error tables, idempotency semantics, and the caveat-enforcement rules. The two calls below are just the quick start. ::: Create a challenge: ```bash curl -X POST https://api.lightningenable.com/api/l402/challenges \ -H "X-API-Key: YOUR_MERCHANT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "resource": "/api/weather/forecast", "priceSats": 50, "description": "7-day weather forecast" }' ``` Verify a token — pass `resource` so the API enforces the macaroon's path caveat server-side (omit it and the bound resource is only reported back, not compared): ```bash curl -X POST https://api.lightningenable.com/api/l402/challenges/verify \ -H "X-API-Key: YOUR_MERCHANT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "macaroon": "AgELbGlnaHRuaW5n...", "preimage": "7f8a9b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a", "resource": "/api/weather/forecast" }' ``` The verify endpoint returns **200 OK for both valid and invalid tokens** — read the `valid` field, not the status code. ## End-to-End Example: Weather Data Agent Here is a complete example of a producer agent that charges for weather data. ### Producer Agent Setup ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "STRIKE_API_KEY": "your-strike-key", "LIGHTNING_ENABLE_API_KEY": "your-merchant-api-key" } } } } ``` ### Producer Agent Behavior ``` Requesting Agent: "I need the 7-day forecast for New York City." Producer Agent (Claude): 1. [Uses l402_producer with action="create", resource="/api/weather/forecast/nyc", priceSats=25, description="7-day NYC weather forecast" ] 2. Returns to requesting agent: "Access to this forecast costs 25 sats. Pay this invoice: lnbc250n1p3... Then send me: L402 AgEL...:your_preimage" Requesting Agent: 3. [Uses pay_l402_challenge(invoice="lnbc250n1p3...", macaroon="AgEL...")] 4. "Here's my token: L402 AgEL...:7f8a9b2c..." Producer Agent (Claude): 5. [Uses l402_producer with action="verify", macaroon="AgEL...", preimage="7f8a9b2c..."] 6. Payment verified! Now fetching the forecast... 7. "NYC 7-Day Forecast: Monday: Sunny, 72F Tuesday: Partly cloudy, 68F ..." ``` ### Consumer Agent Setup The consumer agent only needs a wallet — no subscription required: ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "STRIKE_API_KEY": "consumer-strike-key" } } } } ``` ## Use Cases ### AI Research Agent Charge other agents for access to your curated research database: ``` l402_producer( action="create", resource="/research/market-analysis", priceSats=100, description="Q1 2026 market analysis report" ) ``` ### Code Review Agent Offer automated code review as a paid service: ``` l402_producer( action="create", resource="/services/code-review", priceSats=500, description="Automated code review with security analysis" ) ``` ### Data Aggregation Agent Sell aggregated data from multiple sources: ``` l402_producer( action="create", resource="/data/crypto-sentiment", priceSats=50, description="Real-time crypto sentiment score from 10 sources" ) ``` ### Translation Agent Charge per translation request: ``` l402_producer( action="create", resource="/translate/en-to-ja", priceSats=10, description="English to Japanese translation" ) ``` ## Security Considerations ### Always Verify Before Granting Access Never grant access based on a payer claiming they paid. Always call `l402_producer` with `action="verify"` to cryptographically confirm: - The preimage matches the payment hash (`SHA256(preimage) == payment_hash`) - The macaroon signature is valid (not tampered with) - The macaroon was issued under **your** merchant account (always enforced server-side) - The token has not expired (always enforced server-side) ### Token Binding Each L402 token carries caveats binding it to the **merchant** who issued it, an **expiry**, the **resource** it was issued for, and the **amount** charged. What the verify endpoint enforces server-side depends on what you send: - **Merchant binding** — **always enforced**. Verifying with your API key a token that was issued under a different merchant returns `valid: false`. No opt-out. - **Expiry** — **always enforced**. A token past its validity window (60 minutes by default) returns `valid: false`. - **Resource (path caveat)** — enforced **only when your verify request includes `resource`**. If you omit it, the bound resource is returned in the response but **not compared** — the comparison is your responsibility. - **Amount** — enforced **only when your verify request includes `amountSats`**; otherwise returned but not compared. The MCP `l402_producer` tool with `action="verify"` sends only the macaroon and preimage, so path/amount enforcement does not apply on that route — always compare the `resource` in the tool result against the resource you're about to grant before serving it. When calling the REST endpoint directly, pass `resource` (and `amountSats` if you gate multiple price tiers) to get server-side enforcement. See [caveat enforcement rules](./producer-api-reference#token-reuse-within-the-validity-window) in the Producer API Reference for the full contract. ### Idempotency Keys Send an `Idempotency-Key` on every mint. It is what stops a retry — a timeout, a redeploy mid-request, a client that fires twice — from producing a second invoice your payer could also pay. Without one the server falls back to the client's IP, which works on a single server and not behind a load balancer. Use a key derived from the request you are serving (an order id, a request id), not a fresh UUID per attempt: a new UUID on the retry defeats the point. ## Next Steps - [Sell With Your Agent](/getting-started/sell-with-your-agent) — the same producer flow driven end to end by an MCP agent, from an empty account to a live paid endpoint - [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) — full tool reference - [API Monetization](/products/agentic-commerce/api-monetization) — Monetize existing APIs via native middleware (recommended) or hosted proxy mode - [AI Agent Integration](/products/agentic-commerce/ai-agent-integration) — Consumer-side L402 tools and wallet setup - [L402 API Reference](/api-reference/l402) — Complete L402 protocol reference ============================================================================== # LND Node Setup Source: https://docs.lightningenable.com/products/agentic-commerce/lnd-setup ============================================================================== # LND Node Setup Guide This guide covers connecting the Lightning Enable MCP server to your own LND (Lightning Network Daemon) node. **LND always returns the preimage**, making it the most reliable option for L402 authentication. ## Why Use Your Own LND Node? | Benefit | Description | |---------|-------------| | **Guaranteed L402** | LND always returns preimage - L402 never fails due to wallet limitations | | **You Hold the Keys** | You run the node — you control your funds, not a third party | | **No API Limits** | No rate limits or account restrictions | | **No Custody Risk** | No third-party custody = no counterparty risk | | **No Regulatory Risk** | Not using a money transmitter for payments | :::tip Best For LND is ideal for: - Power users who already run Lightning nodes - Businesses that need 100% L402 reliability - Users who want to run the node and hold the keys themselves - Developers testing L402 implementations ::: --- ## Prerequisites ### You Need 1. **A running LND node** with: - REST API enabled (default port 8080) - Funded channels with outbound liquidity - Admin macaroon access 2. **Network access** from your MCP server to LND: - Same machine: `localhost:8080` - Local network: `192.168.x.x:8080` - Remote: VPN or Tor recommended ### LND Installation Options If you don't have LND yet: | Platform | Recommended Approach | |----------|---------------------| | **Start9** | Built-in LND package | | **Umbrel** | Lightning app from store | | **RaspiBlitz** | Included by default | | **Voltage** | Cloud-hosted LND | | **Manual** | [LND Installation Guide](https://docs.lightning.engineering/lightning-network-tools/lnd/installation) | --- ## Step 1: Enable LND REST API LND's REST API is enabled by default on port 8080. Verify in your `lnd.conf`: ```ini [Application Options] # REST API (enabled by default) restlisten=0.0.0.0:8080 # TLS settings tlsextraip=0.0.0.0 tlsextradomain=your-domain.com ``` If you changed the port, note it for configuration. ### Restart LND After Config Changes ```bash # Stop LND lncli stop # Start LND (method depends on your setup) lnd # or systemctl restart lnd ``` --- ## Step 2: Get Your Admin Macaroon The macaroon is your authentication credential. You need the **admin macaroon** for payments. ### Location by Platform | Platform | Default Path | |----------|-------------| | **Linux** | `~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon` | | **macOS** | `~/Library/Application Support/Lnd/data/chain/bitcoin/mainnet/admin.macaroon` | | **Windows** | `%LOCALAPPDATA%\Lnd\data\chain\bitcoin\mainnet\admin.macaroon` | | **Start9** | SSH in, check `/embassy-data/package-data/volumes/lnd/` | | **Umbrel** | `~/umbrel/app-data/lightning/data/lnd/data/chain/bitcoin/mainnet/admin.macaroon` | | **Docker** | Inside container at `/root/.lnd/data/chain/bitcoin/mainnet/admin.macaroon` | ### Convert Macaroon to Hex The MCP server needs the macaroon in **hex format** (not base64). **Linux/macOS:** ```bash xxd -ps -c 1000 ~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon ``` **Windows PowerShell:** ```powershell [System.BitConverter]::ToString([System.IO.File]::ReadAllBytes("$env:LOCALAPPDATA\Lnd\data\chain\bitcoin\mainnet\admin.macaroon")) -replace '-','' ``` **Docker:** ```bash docker exec lnd xxd -ps -c 1000 /root/.lnd/data/chain/bitcoin/mainnet/admin.macaroon ``` The output looks like: ``` 0201036c6e6402f801030a10b3b1a4d3e5f6789012345678901234561201... ``` Save this string - you'll need it for configuration. --- ## Step 3: Test LND Connection Before configuring the MCP server, verify LND is accessible: ```bash # Test from the machine running MCP curl --insecure \ -H "Grpc-Metadata-macaroon: YOUR_MACAROON_HEX" \ https://localhost:8080/v1/getinfo ``` You should see JSON output with your node info: ```json { "identity_pubkey": "03abc...", "alias": "MyNode", "num_active_channels": 5, ... } ``` ### Common Test Issues | Error | Solution | |-------|----------| | `Connection refused` | LND not running or REST not enabled | | `certificate unknown` | Use `--insecure` or configure TLS cert | | `permission denied` | Wrong macaroon or insufficient permissions | | `timeout` | Firewall blocking port 8080 | --- ## Step 4: Configure MCP Server ### Option A: Environment Variables ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "LND_REST_HOST": "localhost:8080", "LND_MACAROON_HEX": "0201036c6e6402f801030a10..." } } } } ``` ### Option B: Config File (Recommended) Create or edit `~/.lightning-enable/config.json`: ```json { "currency": "USD", "tiers": { "autoApprove": 0.10, "logAndApprove": 1.00, "formConfirm": 10.00, "urlConfirm": 100.00 }, "limits": { "maxPerPayment": 500.00, "maxPerSession": 100.00 }, "session": { "requireApprovalForFirstPayment": false, "cooldownSeconds": 2 }, "wallets": { "lndRestHost": "localhost:8080", "lndMacaroonHex": "0201036c6e6402f801030a10b3b1a4d3e5f6789012345678901234561201...", "priority": "lnd" } } ``` ### Configuration Options | Variable | Config Key | Description | Required | |----------|-----------|-------------|----------| | `LND_REST_HOST` | `lndRestHost` | LND REST API endpoint | Yes | | `LND_MACAROON_HEX` | `lndMacaroonHex` | Admin macaroon in hex | Yes | | `WALLET_PRIORITY` | `priority` | Set to "lnd" | Optional | --- ## Step 5: Verify Connection Restart Claude Code and check the MCP server logs: ``` [LND] Initialized REST client for localhost:8080 Using LND wallet backend LND always returns preimage - L402 fully supported ``` Test with a balance check: ``` User: Check my Lightning balance Claude: [Uses get_balance] Your LND balance: 500,000 sats ``` --- ## Remote LND Access ### Same Local Network ```json { "wallets": { "lndRestHost": "192.168.1.100:8080" } } ``` ### Over Tor (Most Secure) 1. Get your LND Tor address from `lnd.conf` or: ```bash cat ~/.lnd/data/tor/v3_onion_service_address ``` 2. Configure MCP (requires Tor proxy): ```json { "wallets": { "lndRestHost": "abcdef123456.onion:8080" } } ``` ### Over VPN 1. Connect both machines to same VPN 2. Use VPN IP address: ```json { "wallets": { "lndRestHost": "10.8.0.1:8080" } } ``` ### TLS Certificate Handling For remote connections, you may need to handle TLS: **Option 1: Skip TLS verification (development only)** Set environment variable: ``` LND_SKIP_TLS_VERIFY=true ``` **Option 2: Provide TLS cert (production)** Copy `~/.lnd/tls.cert` to MCP machine and set: ``` LND_TLS_CERT_PATH=/path/to/tls.cert ``` --- ## Available Tools with LND | Tool | Description | Works with LND | |------|-------------|----------------| | `pay_invoice` | Pay any Lightning invoice | ✅ Yes (with preimage) | | `get_balance` | View channel balance (BTC only on LND) | ✅ Yes | | `create_invoice` | Create invoice to receive | ✅ Yes | | `check_invoice_status` | Check if invoice paid | ✅ Yes | | `wallet_ops` (`action="send_onchain"`) | Send on-chain Bitcoin | ✅ Yes | | `access_l402_resource` | L402 auto-pay | ✅ Yes | | `pay_l402_challenge` | Manual L402 payment | ✅ Yes | ### Tools NOT Available with LND - `wallet_ops` with `action="price"` - Use Strike for price data - `wallet_ops` with `action="exchange"` - LND is BTC-only --- ## Troubleshooting ### "Failed to connect to LND" **Check LND is running:** ```bash lncli getinfo ``` **Check REST port is open:** ```bash netstat -tlnp | grep 8080 ``` **Check firewall:** ```bash # Linux sudo ufw status # or sudo iptables -L # Allow port 8080 sudo ufw allow 8080 ``` ### "Permission denied" or "Macaroon invalid" **Verify macaroon is correct:** ```bash # Re-export macaroon xxd -ps -c 1000 ~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon ``` **Check macaroon permissions:** ```bash # Bake a new admin macaroon if needed lncli bakemacaroon --save_to=./admin.macaroon \ uri:/lnrpc.Lightning/GetInfo \ uri:/lnrpc.Lightning/ListChannels \ uri:/lnrpc.Lightning/ChannelBalance \ uri:/lnrpc.Lightning/SendPayment \ uri:/invoicesrpc.Invoices/AddInvoice \ uri:/routerrpc.Router/SendPaymentV2 ``` ### "No route found" / "FAILURE_REASON_NO_ROUTE" Your node can't find a path to the destination: 1. **Check outbound liquidity:** ```bash lncli listchannels | jq '.channels[] | {alias: .peer_alias, local: .local_balance, remote: .remote_balance}' ``` 2. **Open new channels** to well-connected nodes 3. **Wait for channel confirmation** if recently opened ### "Invoice expired" The L402 invoice expired before payment completed. This can happen with: - Slow network connections - Large payments requiring route finding Try again - the API will issue a fresh invoice. ### "TLS handshake failed" **For self-signed certs (development):** ```json { "env": { "LND_SKIP_TLS_VERIFY": "true" } } ``` **For production:** Copy your `tls.cert` to the MCP server location. --- ## Security Best Practices ### 1. Use Read-Only Macaroon for Balance Checks If you only need balance checking (not payments): ```bash lncli bakemacaroon --save_to=./readonly.macaroon \ uri:/lnrpc.Lightning/GetInfo \ uri:/lnrpc.Lightning/ListChannels \ uri:/lnrpc.Lightning/ChannelBalance ``` ### 2. Limit Macaroon Scope Create a macaroon with only payment permissions: ```bash lncli bakemacaroon --save_to=./payment.macaroon \ uri:/lnrpc.Lightning/SendPayment \ uri:/routerrpc.Router/SendPaymentV2 \ uri:/lnrpc.Lightning/ChannelBalance ``` ### 3. Use Tor for Remote Access Never expose LND REST API directly to the internet. Use Tor hidden service instead. ### 4. Set Payment Limits in Config The MCP config file provides additional spending controls: ```json { "limits": { "maxPerPayment": 100.00, "maxPerSession": 50.00 } } ``` --- ## Performance Tips ### Channel Management For best L402 performance: - Maintain 5+ active channels - Keep channels balanced (50% local / 50% remote) - Open channels to well-connected LSPs (Lightning Service Providers) ### Recommended LSPs for Connectivity - ACINQ (Phoenix) - Breez - Voltage - LND Labs ### Monitoring Use `lncli` to monitor your node: ```bash # Check pending payments lncli listpayments --max_payments 10 # Check channel health lncli listchannels --active_only # Check forwards (routing) lncli fwdinghistory ``` --- ## Frequently Asked Questions ### Can I use LND on a Raspberry Pi? Yes, but ensure adequate resources: - 4GB+ RAM - SSD storage (not SD card) - Stable internet connection ### Does this work with Core Lightning (CLN)? Not yet. LND is currently the only supported node implementation. CLN support may be added in the future. ### Can I use a hosted LND (Voltage, etc.)? Yes! Voltage and similar services provide the REST API and macaroon. Configure as remote connection. ### What if my node is offline? Payments will fail. Ensure your node has reliable uptime for production use. ### Is my macaroon safe in the config file? The config file is stored locally on your machine. Keep it protected like any credential file: - Don't commit to git - Use file permissions (chmod 600) - Consider encrypted storage --- ## Next Steps - [AI Agent Integration](/products/agentic-commerce/ai-agent-integration) - Using L402 tools - [AI Spending Security](/products/agentic-commerce/ai-spending-security) - Budget configuration - [Wallet Configuration](/products/agentic-commerce/mcp-wallet-setup) - Other wallet options ============================================================================== # MCP Complete Guide Source: https://docs.lightningenable.com/products/agentic-commerce/mcp-complete-guide ============================================================================== # Lightning Enable MCP - Complete Guide The Lightning Enable MCP (Model Context Protocol) server enables AI agents to interact with the Lightning Network. It's **free and open source** with comprehensive Lightning wallet capabilities. :::info Open Source The MCP server is fully open source: [github.com/refined-element/lightning-enable-mcp](https://github.com/refined-element/lightning-enable-mcp). Available on NuGet, PyPI, and Docker. ::: ## Overview ``` ┌──────────────────────────────────────────────────────────────────────────┐ │ Lightning Enable MCP │ ├──────────────────────────────────────────────────────────────────────────┤ │ AI Agent (Claude, etc.) │ │ ↓ │ │ MCP Server (wallet priority: LND > NWC > Strike > OpenNode) │ │ ↓ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ LND │ │ NWC │ │ Strike │ │ OpenNode │ │ │ │ Wallet │ │ Wallet │ │ Wallet │ │ Wallet │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ ↓ ↓ ↓ ↓ │ │ Lightning Network │ └──────────────────────────────────────────────────────────────────────────┘ ``` **Wallet Priority:** When multiple wallets are configured, the MCP server selects them in order: **LND > NWC > Strike > OpenNode**. This order is optimized for L402 compatibility (LND and NWC always return preimages). Override with the `WALLET_PRIORITY` environment variable or the `priority` field in your config file. ## Installation ### .NET (Windows, Linux, macOS) ```bash dotnet tool install -g LightningEnable.Mcp ``` ### Python (All Platforms) ```bash pip install lightning-enable-mcp ``` Or use uvx for no-install execution: ```bash uvx lightning-enable-mcp ``` :::note NWC wallets The base install works on every platform (including Windows) with no build toolchain. If you connect a **Nostr Wallet Connect (NWC)** wallet, install the optional extra: `pip install lightning-enable-mcp[nwc]`. Other wallet types (LND, Strike, OpenNode) don't need it. (.NET is unaffected — it uses managed crypto.) ::: ## Quick Start **Claude Code** users: register the server with `claude mcp add --transport stdio lightning-enable --env STRIKE_API_KEY=your-key -- lightning-enable-mcp` (or a project `.mcp.json`) — see [Claude Code Setup](/products/agentic-commerce/claude-code-setup). Claude Code does **not** read `claude_desktop_config.json`. **Claude Desktop** users: add to your Claude Desktop config: **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` **Linux:** `~/.config/claude/claude_desktop_config.json` ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "STRIKE_API_KEY": "your-strike-api-key" } } } } ``` --- ## All Available Tools ### Tool Profiles The MCP server ships three tool profiles, selected once at startup with `LIGHTNING_ENABLE_TOOL_PROFILE`: ```bash export LIGHTNING_ENABLE_TOOL_PROFILE=standard # default — omit this line to get the same result ``` | Profile | Surface | Use it when | |---|---|---| | `lite` | Pay, check the wallet, budget, receipts, wallet setup | You want the smallest possible footprint — an agent that can pay and check its own wallet, and nothing else. | | `standard` *(default)* | Every current tool (listed below) | The current, canonical surface. Everything the server can do, addressed through a small number of action-based tools instead of one tool per operation. | | `full` | `standard` plus every pre-consolidation name as a **deprecated alias** | You're running a prompt or integration written against the old per-operation tool names (`configure_budget`, `create_l402_challenge`, …). Aliases forward to the new tools and keep working, but are removed in **v3.0.0** — migrate when you can. | **`lite`:** `pay_invoice`, `access_l402_resource`, `get_balance`, `budget`, `receipts`, `setup_wallet` **`standard` (default):** `access_l402_resource`, `pay_invoice`, `pay_l402_challenge`, `test_l402_payment`, `get_balance`, `budget`, `receipts`, `create_invoice`, `check_invoice_status`, `verify_confirmation_code`, `discover_api`, `create_lightning_enable_account`, `setup_wallet`, `wallet_ops`, `l402_producer`, `agent_services` The lists above are the source of truth; other pages link here rather than restating counts, which drift. The server's own tool listing is authoritative for the installed version. ### Tool Availability by Wallet | Tool | Strike | OpenNode | LND | NWC (CoinOS/CLINK) | NWC (Alby) | NWC (Primal) | API key | |------|--------|----------|-----|-------------------|------------|--------------|---------| | `pay_invoice` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | No | | `access_l402_resource` | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | No | | `pay_l402_challenge` | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | No | | `test_l402_payment` | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | No | | `get_balance` | ✅ (multi-currency) | ✅ | ✅ | ✅ | ✅ | ✅ | No | | `budget` (`action=status\|tighten`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | No | | `receipts` (`source=durable\|session`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | No | | `create_invoice` | ✅ | ✅ | ✅ | ✅* | ✅* | ✅* | No | | `check_invoice_status` | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | No | | `verify_confirmation_code` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | No | | `discover_api` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | No | | `create_lightning_enable_account` | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | No | | `setup_wallet` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | No | | `wallet_ops` (`action=price\|exchange\|send_onchain`) | ✅ | ❌** | ✅ (send_onchain only) | ❌** | ❌** | ❌** | No | | `l402_producer` (`action=create\|verify\|configure_receive\|status\|create_proxy\|add_endpoint\|publish\|list_challenges`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | **Yes, every action** | | `agent_services` (`action=discover\|request\|settle\|publish\|unpublish\|attest\|reputation`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Partial — see [ASA tools](#agent_services) | \*NWC invoice creation depends on wallet support. \*\*`price` and `exchange` are Strike-only regardless of wallet; `send_onchain` also works on LND. L402 requires preimage return: LND always works, Strike works, CoinOS/CLINK work, Alby works, OpenNode/Primal don't return preimage. ### Deprecated aliases Two generations of renames, both kept working so an old prompt still makes sense — the mapping is what changed, not the behavior: | Deprecated name | Current call | Since | |---|---|---| | `check_wallet_balance`, `get_all_balances` | `get_balance` | v1.17.0 | | `confirm_payment` | `verify_confirmation_code` | v1.17.0 | | `get_payment_history` | `receipts(source="session")` | Tool consolidation | | `get_receipts` | `receipts(source="durable")` | Tool consolidation | | `get_budget_status` | `budget(action="status")` | Tool consolidation | | `configure_budget` | `budget(action="tighten")` | Tool consolidation | | `get_btc_price` | `wallet_ops(action="price")` | Tool consolidation | | `exchange_currency` | `wallet_ops(action="exchange")` | Tool consolidation | | `send_onchain` | `wallet_ops(action="send_onchain")` | Tool consolidation | | `create_l402_challenge` | `l402_producer(action="create")` | Tool consolidation | | `verify_l402_payment` | `l402_producer(action="verify")` | Tool consolidation | | `discover_agent_services` | `agent_services(action="discover")` | Tool consolidation | | `request_agent_service` | `agent_services(action="request")` | Tool consolidation | | `settle_agent_service` | `agent_services(action="settle")` | Tool consolidation | | `publish_agent_capability` | `agent_services(action="publish")` | Tool consolidation | | `unpublish_agent_capability` | `agent_services(action="unpublish")` | Tool consolidation | | `publish_agent_attestation` | `agent_services(action="attest")` | Tool consolidation | | `get_agent_reputation` | `agent_services(action="reputation")` | Tool consolidation | The 16 "Tool consolidation" rows are available under `LIGHTNING_ENABLE_TOOL_PROFILE=full` only — `standard` and `lite` expose just the current names on the left of the "Current call" column above. All 18 aliases are scheduled for removal in **v3.0.0**. This page is the canonical, authoritative tool list — other docs pages link here rather than repeat tool counts. --- ## Onboarding ### setup_wallet Configures a wallet into `~/.lightning-enable/config.json` without hand-editing JSON — usually the first tool call of a session on a fresh machine, and the one the [`producer-setup` skill](https://github.com/refined-element/lightning-enable-skills/tree/main/skills/producer-setup) opens with (see [Sell With Your Agent](/getting-started/sell-with-your-agent)). **Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `walletType` | string | Yes | `strike`, `opennode`, `nwc`, or `lnd` | | `credential` | string | Yes | The API key (Strike/OpenNode), the `nostr+walletconnect://...` string (NWC), or the LND macaroon (LND) | | `lndRestHost` | string | If `walletType=lnd` | LND REST API host | | `maxPerPaymentSats` / `maxPerSessionSats` | int | No | Sats-native spend ceiling — see [sats-native budgets](#sats-native-budgets) below. Omit to keep the existing config-file limits (USD or sats) untouched | The tool applies the same first-run file lockdown as any other config write (`chmod 0600` on POSIX, `icacls /inheritance:r /grant {user}:F` on Windows — see [Config file perms](#environment-variables-reference)) and never echoes the credential back in its result — the response confirms what was configured (wallet type, provider) without repeating the secret. :::note Never returns the credential Like every other part of the MCP server, `setup_wallet`'s response never contains the value you passed in `credential` — only whether the write succeeded and which wallet type is now active. ::: ## Core Tools (All Wallets) ### pay_invoice Pay any Lightning invoice and get the preimage as proof of payment. **Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `invoice` | string | Yes | - | BOLT11 Lightning invoice | | `max_sats` | int | No | 1000 | Maximum payment allowed (Python package; the .NET tool relies on budget-service limits) | | `confirmationNonce` | string | No | - | Confirmation code from the server console (`confirmation_nonce` in Python). Required on the retry when the first call returned `requiresConfirmation=true` — see [Out-of-Band Confirmation](#out-of-band-confirmation) | **Example:** ``` User: Pay this invoice: lnbc500n1p3... Claude: [Uses pay_invoice] Payment successful! - Amount: 500 sats - Preimage: 7f8a9b2c3d4e5f... - Provider: Strike ``` **Response:** ```json { "success": true, "provider": "Strike", "payment": { "preimage": "7f8a9b2c3d4e5f...", "amountSats": 500 }, "message": "Payment successful! Paid 500 sats." } ``` --- ### get_balance Get the connected wallet's balance: the sats balance plus, where available, all currency balances (multi-currency for Strike, a single BTC entry otherwise) and the session spend summary. Supersedes the old `check_wallet_balance` and `get_all_balances` tools (v1.17.0) — it returns the superset of what both returned, and the old names keep working as deprecated aliases until v3.0.0. **Parameters:** None required **Example:** ``` User: What's my wallet balance? Claude: [Uses get_balance] Wallet Balance: 50,000 sats (~$50 USD) Provider: Strike Strike balances: $127.45 USD · 0.00050000 BTC ``` **Response:** ```json { "success": true, "provider": "Strike", "balance": { "sats": 50000, "btc": 0.0005 }, "balances": [ { "currency": "USD", "available": 127.45, "formatted": "127.45 USD" }, { "currency": "BTC", "available": 0.0005, "formatted": "0.00050000 BTC (50,000 sats)" } ], "message": "Balance: 50,000 sats" } ``` --- ### receipts Read the payment record, from either of two sources selected with `source`. Replaces the two formerly separate tools `get_payment_history` (now `source="session"`) and `get_receipts` (now `source="durable"`). **Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `source` | string | No | `durable` | `durable` — the persistent receipt log (`lightning-enable://receipts`, survives restarts). `session` — recent payments made in this running process only (in-memory, cleared on restart). | | `limit` | int | No | 10 | Maximum records to return | **Example:** ``` User: Show my recent payments Claude: [Uses receipts with source="session"] Recent Payments: 1. 500 sats - lnbc500n1... - 5 min ago 2. 100 sats - lnbc100n1... - 1 hour ago 3. 1000 sats - lnbc1u1... - 2 hours ago Total: 1,600 sats across 3 payments ``` :::tip Also readable as an MCP Resource The durable log is also exposed as an MCP **Resource** at `lightning-enable://receipts`, so a client that reads resources rather than calling tools can pull the same data without a tool call. ::: --- ### budget Read or tighten spending limits, selected with `action`. Replaces the two formerly separate tools `get_budget_status` (now `action="status"`) and `configure_budget` (now `action="tighten"`). **Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `action` | string | Yes | `status` (read-only) or `tighten` (lower a runtime cap) | | `maxPerPaymentSats` / `maxPerSessionSats` | int | Only for `action="tighten"` | New, **lower** sats caps. Rejected if either value is above the current effective cap — this action can never raise a limit. Parameter names: `per_request` / `per_session` in the Python package, `perRequest` / `perSession` in .NET, still accepted as aliases. | `status` is **read-only** — limits can only be *raised* by editing the config file directly; `budget(action="tighten")` can only ever lower the runtime sats caps. **Example — status:** ``` User: What are my spending limits? Claude: [Uses budget with action="status"] Budget Configuration (READ-ONLY): - Config file: ~/.lightning-enable/config.json - Auto-approve: up to $0.10 - Log & approve: $0.10 - $1.00 - Requires out-of-band confirmation code: above $1.00 - Maximum per payment: $500.00 - Maximum per session: $100.00 Session: - Spent: $0.45 (4,500 sats) - Remaining: $99.55 Note: AI cannot RAISE budget limits. budget(action="tighten") can only tighten the runtime sats caps; edit config.json to raise limits. ``` **Example — tighten:** ``` User: Lower my per-request cap to 5,000 sats for this session. Claude: [Uses budget with action="tighten", maxPerPaymentSats=5000] Runtime per-payment cap tightened to 5,000 sats for this session. (This cannot be undone by calling budget again with a higher value — only editing config.json can raise it back.) ``` See [sats-native budgets](#sats-native-budgets) below for `maxPerPaymentSats` / `maxPerSessionSats` as **operator** config-file keys, which is a different (higher-authority) thing from this tool's runtime tightening. --- ### verify_confirmation_code Verify whether a payment confirmation code (relayed by the human from the server console) is still valid and what it authorizes. **Verification only — it never executes a payment.** To actually pay, re-call the original payment tool with the `confirmationNonce` (.NET) / `confirmation_nonce` (Python) parameter. Renamed from `confirm_payment` in v1.17.0 (the old name still works as a deprecated alias until v3.0.0). **Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `nonce` | string | Yes | The confirmation code the human read from the server console | **Response (valid):** ```json { "valid": true, "amount_sats": 2500, "tool": "pay_invoice", "message": "Code verified — NOTHING HAS BEEN PAID. To execute, call pay_invoice again with confirmation_nonce=." } ``` --- ## Invoice Tools (Strike, OpenNode, LND) ### create_invoice Create a Lightning invoice to receive payments. Returns a BOLT11 string to share with the payer. **Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `amountSats` | long | Yes | - | Amount to receive in satoshis | | `memo` | string | No | null | Description for the invoice | | `expirySecs` | int | No | 3600 | Invoice expiry (default 1 hour) | **Example:** ``` User: Create an invoice for 1000 sats for "Coffee payment" Claude: [Uses create_invoice] Invoice Created! - Amount: 1,000 sats - Memo: Coffee payment - Expires: 1 hour - Invoice ID: inv_abc123... BOLT11 Invoice (share with payer): lnbc10u1p3xyza... Share this invoice with the payer, or display as a QR code. ``` **Response:** ```json { "success": true, "provider": "Strike", "invoice": { "id": "inv_abc123", "bolt11": "lnbc10u1p3xyza...", "amountSats": 1000, "expiresAt": "2024-01-15T14:30:00Z" }, "message": "Invoice created for 1000 sats. Share the bolt11 string with the payer." } ``` --- ### check_invoice_status Check if a previously created invoice has been paid. **Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `invoiceId` | string | Yes | Invoice ID from create_invoice | **Example:** ``` User: Has the coffee invoice been paid? Claude: [Uses check_invoice_status with invoiceId="inv_abc123"] Invoice Status: PAID! ✅ - Amount: 1,000 sats - Paid at: 2024-01-15 13:45:22 UTC ``` **Response:** ```json { "success": true, "provider": "Strike", "invoice": { "id": "inv_abc123", "state": "paid", "isPaid": true, "isPending": false, "amountSats": 1000, "paidAt": "2024-01-15T13:45:22Z" }, "message": "Invoice inv_abc123 has been PAID!" } ``` --- ## wallet_ops Strike- and LND-specific operations. One consolidated tool for the operations that only some wallets support, selected with `action`. Replaces the three formerly separate tools `get_btc_price` (now `action="price"`), `exchange_currency` (now `action="exchange"`), and `send_onchain` (now `action="send_onchain"`). `price` and `exchange` are Strike-only regardless of wallet. `send_onchain` works with Strike and LND. ### action="price" Get the current Bitcoin price in USD. **Parameters:** `action="price"` only — no other parameters **Example:** ``` User: What's the current Bitcoin price? Claude: [Uses wallet_ops with action="price"] Current BTC Price: $97,500.00 USD Source: Strike Timestamp: 2024-01-15 13:30:00 UTC ``` **Response:** ```json { "success": true, "provider": "Strike", "ticker": { "btcUsd": 97500.00, "timestamp": "2024-01-15T13:30:00Z" }, "message": "Current BTC price: $97,500.00 USD" } ``` --- ### action="exchange" Convert between USD and BTC within your Strike wallet. **Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `sourceCurrency` | string | Yes | Currency to convert from: `USD` or `BTC` | | `targetCurrency` | string | Yes | Currency to convert to: `BTC` or `USD` | | `amount` | decimal | Yes | Amount in source currency | **Example — Buy Bitcoin:** ``` User: Convert $50 to Bitcoin Claude: [Uses wallet_ops with action="exchange", sourceCurrency=USD, targetCurrency=BTC, amount=50] Exchange Complete! - Converted: $50.00 USD - Received: 0.00051282 BTC (51,282 sats) - Rate: $97,500/BTC - Fee: $0.25 ``` **Example — Sell Bitcoin:** ``` User: Sell 0.001 BTC for dollars Claude: [Uses wallet_ops with action="exchange", sourceCurrency=BTC, targetCurrency=USD, amount=0.001] Exchange Complete! - Converted: 0.001 BTC (100,000 sats) - Received: $97.00 USD - Rate: $97,500/BTC - Fee: $0.50 ``` --- ### action="send_onchain" Send an on-chain Bitcoin payment to a Bitcoin address (not Lightning). Works with Strike and LND. **Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `address` | string | Yes | Bitcoin address (bc1q..., 3..., or 1...) | | `amountSats` | long | Yes | Amount to send in satoshis | | `confirmationNonce` | string | No | Confirmation code from the server console (`confirmation_nonce` in Python). Omit on the first call to request one; **always required to actually send** | :::warning Always Confirmed On-chain payments are irreversible, so `wallet_ops(action="send_onchain")` **always** requires an out-of-band confirmation code — even for small amounts — and **fails closed** if the budget service is unavailable. See [Out-of-Band Confirmation](#out-of-band-confirmation). ::: **Example:** ``` User: Send 50000 sats to bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh Claude: [Uses wallet_ops with action="send_onchain"] On-Chain Payment Sent! - Amount: 50,000 sats (0.0005 BTC) - Address: bc1qxy2kg... - Network Fee: 500 sats - Status: COMPLETED - Transaction ID: abc123def456... ``` :::warning On-Chain vs Lightning On-chain payments are slower (10+ minutes) and have higher fees than Lightning. Use Lightning (`pay_invoice`) when possible. ::: --- ## L402 Tools (Free) These tools are included for free with the MCP server. No license purchase or subscription required. :::info L402-Compatible Wallets Required L402 requires a wallet that returns the payment preimage: - **LND (self-hosted)** - Always works, guaranteed L402 - **CoinOS** - Free, easy, works for L402 - **CLINK** - Nostr-native, works for L402 - **Strike** - Returns preimage, works for L402 - **Alby** - ✅ Works OpenNode and Primal don't return preimages and **cannot be used for L402**. ::: ### access_l402_resource Fetch a URL with automatic L402 payment handling. When the API returns 402 Payment Required, the MCP automatically pays the invoice and retries with the L402 credential. **Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `url` | string | Yes | - | URL to fetch | | `method` | string | No | GET | HTTP method | | `headers` | string | No | null | JSON object of headers | | `body` | string | No | null | Request body | | `maxSats` | int | No | 1000 | Maximum payment allowed | | `confirmationNonce` | string | No | - | Confirmation code from the server console (`confirmation_nonce` in Python). Required on the retry when the first call returned `requiresConfirmation=true` | **Example:** ``` User: Fetch premium data from https://api.example.com/l402/data Claude: [Uses access_l402_resource] The API required a 50 sat payment which was automatically paid. Response: { "premium": "data...", "analysis": "..." } Payment Details: - Amount: 50 sats ($0.05) - L402 token cached for future requests ``` --- ### pay_l402_challenge Manually pay an L402 (or MPP) invoice when you have the invoice — and, for L402, the macaroon — as separate components. **Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `invoice` | string | Yes | - | BOLT11 Lightning invoice | | `macaroon` | string | No | - | Base64-encoded macaroon. **Optional — omit for MPP mode** | | `maxSats` | int | No | 1000 | Maximum payment allowed | | `confirmationNonce` | string | No | - | Confirmation code from the server console (`confirmation_nonce` in Python). Required on the retry when confirmation was requested | **Returns:** L402 credential in format `macaroon:preimage` (L402 mode), the bare preimage (legacy MPP mode), or a single-use `Authorization: Payment ` credential (modern MPP draft-00 mode) **MPP support:** the `macaroon` parameter is optional. When you omit it, the tool operates in MPP (Machine Payments Protocol) mode — some paid APIs issue a plain Lightning invoice and accept the payment preimage alone as the access token, with no macaroon involved. Provide the macaroon for standard L402 challenges; omit it when the challenge only gave you an invoice. **Modern `Payment` challenges (v1.24.0+):** for APIs using the current MPP draft-00 wire format (`draft-httpauth-payment-00`), pass the raw challenge header via the optional `challengeHeader` (.NET) / `challenge_header` (Python) parameter. The tool runs client-side safety checks before paying (expiry, `intent: charge`, sat currency, declared amount must agree with the invoice) and returns the single-use modern credential, which is never cached or replayed. `access_l402_resource` handles modern `Payment` challenges automatically and surfaces the server's `Payment-Receipt` header in its result (`paymentReceipt` — payment hash only, never the preimage). --- ### discover_api Search the L402 API registry to find available paid APIs by keyword or category, or fetch a specific API's manifest for full endpoint details and pricing. **Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `query` | string | No | - | Search the registry by keyword (e.g., "weather", "ai") | | `category` | string | No | - | Filter registry results by category | | `url` | string | No | - | Fetch a specific API's manifest directly | | `budgetAware` | bool | No | true | Annotate results with affordable call counts | **Example — Search the registry:** ``` User: Find me some weather APIs I can pay for Claude: [Uses discover_api with query="weather"] Found 3 weather APIs in the L402 registry: 1. Weather Data API — 5 sats/request (1,600 calls affordable) 2. Storm Tracker Pro — 25 sats/request (320 calls affordable) 3. Climate Analytics — 100 sats/request (80 calls affordable) Want me to get full details on any of these? ``` **Example — Get full manifest:** ``` User: Show me the full details for Weather Data API Claude: [Uses discover_api with url="https://api.lightningenable.com/l402/proxy/weather-api/.well-known/l402-manifest.json"] Weather Data API — 3 endpoints: - GET /v1/current — Current weather (5 sats) - GET /v1/forecast — 7-day forecast (10 sats) - GET /v1/historical — Historical data (25 sats) ``` **Registry URL:** Defaults to `https://api.lightningenable.com`. Override with `L402_REGISTRY_URL` or `LIGHTNING_ENABLE_API_URL` environment variable. **Manifest probing (v1.24.1+):** when you pass a base `url`, the tool probes `/.well-known/l402-manifest.json`, `/.well-known/l402.json`, `/l402-manifest.json`, and `/l402.json` in order, and only accepts documents that actually describe a service or its endpoints (protocol signposts are skipped). --- ## l402_producer Agentic Commerce producer tool. One consolidated tool for the entire producer side of L402 — everything your agent needs to **charge** other agents and users, configure a receive lane, and stand up a monetized endpoint — selected with `action`. Requires `LIGHTNING_ENABLE_API_KEY` with an Agentic Commerce subscription for **every** action; there is no free tier of this tool. :::info Agents That Earn The consumer tools above let agents spend. `l402_producer` lets an agent **earn** — including setting up the whole receive path itself. Together, they enable true agent-to-agent commerce — AI agents that autonomously buy and sell services using Lightning payments. See [Sell With Your Agent](/getting-started/sell-with-your-agent) for the full end-to-end walkthrough that chains every action below. ::: | `action` | Replaces | Does | |---|---|---| | `create` | `create_l402_challenge` | Mint a Lightning invoice + macaroon to charge for a resource | | `verify` | `verify_l402_payment` | Verify an L402 token (macaroon + preimage) before granting access | | `configure_receive` | *(new)* | Save a receive-side credential (NWC connection string, or Strike/OpenNode key) and switch the account onto that lane | | `status` | *(new)* | Confirm the receive lane is live and reachable before minting anything against it | | `create_proxy` | *(new)* | Wrap an existing API in an L402-gated proxy | | `add_endpoint` | *(new)* | Register a specific path, method, price, and description in the proxy's manifest | | `publish` | *(new)* | Enable the manifest and, optionally, list the proxy in the public L402 registry | | `list_challenges` | *(new)* | Read back the challenge feed — what's been minted and what's been paid | ### action="create" Create an L402 payment challenge (Lightning invoice + macaroon) to charge another agent or user. **Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `resource` | string | Yes | - | Resource identifier (URL, service name, or description) | | `priceSats` | long | Yes | - | Price in satoshis to charge | | `description` | string | No | null | Description shown on the Lightning invoice | **Example:** ``` User: Charge 50 sats for access to the forecast data Claude: [Uses l402_producer with action="create"] L402 challenge created! - Invoice: lnbc500n1p3xyza... - Price: 50 sats - Resource: /api/weather/forecast Share the invoice with the payer. After they pay, use l402_producer with action="verify" to confirm before granting access. ``` **Response:** ```json { "success": true, "challenge": { "invoice": "lnbc500n1p3xyza...", "macaroon": "AgELbGlnaHRuaW5n...", "paymentHash": "abc123def456...", "expiresAt": "2026-03-13T14:30:00Z" }, "resource": "/api/weather/forecast", "priceSats": 50, "message": "L402 challenge created for 50 sats. Share the invoice with the payer." } ``` --- ### action="verify" Verify an L402 token (macaroon + preimage) to confirm payment was made before granting access. **Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `macaroon` | string | Yes | - | Base64-encoded macaroon from the L402 token | | `preimage` | string | Yes | - | Hex-encoded preimage (proof of payment) | **Example:** ``` User: The agent sent this L402 token — verify it Claude: [Uses l402_producer with action="verify"] Payment verified! The payer paid 50 sats for /api/weather/forecast. Granting access now. ``` **Response (valid):** ```json { "success": true, "valid": true, "resource": "/api/weather/forecast", "message": "Payment verified. The payer has paid — you can now grant access to the resource." } ``` **Response (invalid):** ```json { "success": true, "valid": false, "message": "Payment verification failed. The token is invalid or the invoice has not been paid. Do NOT grant access." } ``` :::warning Always Verify Never grant access based on a payer claiming they paid. Always call `l402_producer` with `action="verify"` to cryptographically confirm payment before granting access. ::: ### action="configure_receive", "status", "create_proxy", "add_endpoint", "publish", "list_challenges" These six actions are new — there is no legacy single-purpose tool they rename. Together they let an agent take an API from "not connected to Lightning Enable at all" to "live, priced, and discoverable" without the dashboard: - **`configure_receive`** — wraps `PUT /api/merchant/nwc-connection` (or the Strike/OpenNode key endpoint) plus `PUT /api/merchant/payment-provider`. - **`status`** — wraps `GET /api/merchant/quickstart`; confirm the receive lane actually took before anything downstream depends on it. - **`create_proxy`** — wraps `POST /api/proxy`. - **`add_endpoint`** — wraps `POST /api/proxy/{proxyId}/manifest/endpoints`. - **`publish`** — wraps `PUT /api/proxy/{proxyId}/manifest/settings`. - **`list_challenges`** — wraps `GET /api/l402/challenges`. Full parameters, request/response shapes, and a worked example chaining all eight actions (including `create`/`verify` above) are in [Sell With Your Agent](/getting-started/sell-with-your-agent); the underlying REST contract for each is in the [Producer API Reference](/products/agentic-commerce/producer-api-reference) and [Proxy Configuration](/products/agentic-commerce/proxy-configuration). See [L402 Producer API](/products/agentic-commerce/l402-producer-api) for the complete producer guide with end-to-end examples. --- ## agent_services Agent Service Agreements (ASA) tool. ASA shipped 2026-04-18 and are live at `wss://agents.lightningenable.com` / [nostrwolfe.com](https://nostrwolfe.com). They let agents discover each other, request services, settle via L402 payment over Nostr, and build on-protocol reputation (flow: discover → request → settle → attest). One consolidated tool, `agent_services`, now covers all seven actions — selected with `action`. **Discovery, reputation reads, and L402 settlement (`action="discover"`, `action="reputation"`, `action="settle"`) work against the public registry with just a wallet; requesting, publishing, and unpublishing (`action="request"`, `action="publish"`, `action="unpublish"`, `action="attest"`) require `LIGHTNING_ENABLE_API_KEY`.** Wallet type never affects the API-key check. | `action` | Replaces | Purpose | API key | |------|---------|---------|---------| | `request` | `request_agent_service` | **Entry point.** Sends a service request (kind 38401 event) referencing the provider's capability. | Required | | `discover` | `discover_agent_services` | Discover agent capabilities on Nostr by category, hashtag, or keyword (kind 38400 events). | — | | `settle` | `settle_agent_service` | Settle an agreement via L402 payment (consumer/requester side) using the same auto-pay flow as `access_l402_resource`. | — | | `publish` | `publish_agent_capability` | Advertise your agent's service so other agents can discover it (kind 38400 event; optionally auto-creates an L402 proxy). | Required | | `unpublish` | `unpublish_agent_capability` | Take a published listing down: retires the L402 proxy and publishes a NIP-09 deletion plus a status=removed replacement, so other agents stop seeing a dead listing. | Required | | `attest` | `publish_agent_attestation` | Publish a review (rating 1–5) for an agent after a completed agreement (kind 38403 event) to build its reputation. | Required | | `reputation` | `get_agent_reputation` | Fetch an agent's reputation score and reviews (queries kind 38403 attestations for a pubkey). | — | ### action="request" Sends a service request (kind 38401 event) referencing the provider's capability. The full ASA flow is **discover → request → settle → attest**. If the provider has an L402 endpoint, you can skip this step and use `action="settle"` directly. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `capabilityEventId` | string | Yes | - | Event ID of the capability to request | | `budgetSats` | int | Yes | - | Maximum budget in satoshis | | `parameters` | string | No | - | Additional parameters as JSON | ### action="discover" | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `category` | string | No | - | Filter by service category (e.g., `ai`, `data`, `translation`) | | `hashtags` | string[] | No | - | Filter by hashtags | | `query` | string | No | - | Search query | | `limit` | int | No | 20 | Maximum results to return | ### action="settle" Pays the L402 endpoint specified in the agreement, completing the service transaction. If you are the **provider** (selling a service), use `l402_producer` with `action="create"` to generate an invoice and `action="verify"` to confirm payment instead. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `l402Endpoint` | string | Yes | - | L402 endpoint URL from the service agreement | | `method` | string | No | `GET` | HTTP method (GET, POST) | | `body` | string | No | - | Optional request body for POST requests | | `agreementId` | string | No | - | Agreement event ID for tracking | | `maxSats` | int | No | 1000 | Maximum satoshis to pay | ### action="publish" | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `serviceId` | string | Yes | - | Unique service identifier (used as d-tag) | | `categories` | string[] | Yes | - | Service categories (e.g., `['ai', 'translation']`) | | `content` | string | Yes | - | Description of the service | | `priceSats` | int | Yes | - | Price per request in satoshis | | `l402Endpoint` | string | No | - | L402 endpoint URL for payment settlement | | `targetUrl` | string | No | - | Target API URL (if auto-creating an L402 proxy via Lightning Enable) | | `hashtags` | string[] | No | - | Hashtags for discoverability | ### action="unpublish" Works for marketplace listings created via the L402 proxy/dashboard pipeline. | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `serviceId` | string | Yes | - | The listing's identifier — its Nostr d-tag / proxy id | | `reason` | string | No | - | Optional free-text reason recorded on the removal event | ### action="attest" | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `subjectPubkey` | string | Yes | - | Pubkey of the agent being reviewed | | `agreementId` | string | Yes | - | Event ID of the agreement this review is for | | `rating` | int | Yes | - | Rating from 1–5 | | `content` | string | Yes | - | Free-text review content | ### action="reputation" | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `pubkey` | string | Yes | - | Pubkey of the agent to query reputation for | | `limit` | int | No | 20 | Maximum number of attestations to return | --- ## Wallet Configuration ### Strike (Recommended for USD Users) Best for users who want USD balance management, BTC price tracking, and easy on/off ramps. ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "STRIKE_API_KEY": "your-strike-api-key" } } } } ``` **Setup:** 1. Create account at https://strike.me 2. Get API key from https://dashboard.strike.me 3. Fund your account with BTC :::info Strike Payment Polling Strike payments are polled with **exponential backoff** (1s, 1s, 1s, 2s, 2s, 2s, 4s cap) to minimize API calls while detecting completion quickly. ::: **Available Tools:** All tools including L402 (Strike returns preimage via `lightning.preImage`) --- ### NWC - CoinOS or CLINK (Easy L402) Good for L402 auto-pay. CoinOS, CLINK, and Alby Hub return preimage which is required for L402. ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "NWC_CONNECTION_STRING": "nostr+walletconnect://..." } } } } ``` **Setup:** 1. Create wallet at https://coinos.io (free, recommended) or https://clink.tools (Nostr) 2. Go to Settings → NWC → Create connection 3. Enable **auto-pay** for the connection 4. Copy connection string **Available Tools:** All tools including L402 (CoinOS/CLINK confirmed working) --- ### NWC (Nostr Wallet Connect) Best when you hold the keys to your own wallet. L402 support depends on wallet. ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "NWC_CONNECTION_STRING": "nostr+walletconnect://pubkey?relay=wss://relay.example.com&secret=xxx" } } } } ``` **Setup:** 1. Use a compatible wallet (CoinOS, CLINK, Alby Hub, or Primal) 2. Create NWC connection in wallet settings 3. Copy connection string :::info NWC Connection Timeouts The MCP server uses tuned WebSocket timeouts for NWC: **3 seconds** for the NIP-47 INFO encryption auto-detect, then roughly **30 seconds** (.NET) or **60 seconds** (Python) waiting for a payment response. If your relay is slow or unreachable, the connection fails gracefully rather than hanging. ::: **L402 Compatibility:** | Wallet | L402 Works | Cost | |--------|------------|------| | **CoinOS** | ✅ Yes | Free | | **CLINK** | ✅ Yes | Free (Nostr users) | | **Alby Hub** | ✅ Yes | Self-host or paid cloud | | **Primal** | ❌ No | Free (no preimage) | :::tip CoinOS or CLINK for Free L402 CoinOS and CLINK are completely free and return preimages, so L402 works! Strike also returns preimages. OpenNode and Primal don't return preimages. ::: --- ## Budget Configuration Spending limits are configured in `~/.lightning-enable/config.json` (created automatically on first run, or written by [`setup_wallet`](#setup_wallet)): ```json { "currency": "USD", "tiers": { "autoApprove": 0.10, "logAndApprove": 1.00, "formConfirm": 10.00, "urlConfirm": 100.00 }, "limits": { "maxPerPayment": 500.00, "maxPerSession": 100.00 }, "session": { "cooldownSeconds": 2, "requireApprovalForFirstPayment": false }, "confirmation": { "channel": "stderr" } } ``` (The `session` values shown are the defaults: a 2-second cooldown between payments, and no forced confirmation on the first payment of a session — set `requireApprovalForFirstPayment` to `true` to opt in.) ### Approval Tiers | Amount (USD) | Behavior | |--------------|----------| | ≤ $0.10 | Auto-approved silently | | $0.10 - $1.00 | Approved but logged | | $1.00 - $500.00 | Out-of-band confirmation required (see below) | | > $500.00 | Blocked entirely (`maxPerPayment`) | ### Sats-Native Budgets `limits.maxPerPayment` / `limits.maxPerSession` and `tiers.autoApprove` above are USD, converted to sats at call time from a live BTC price feed. If you'd rather set the ceiling directly in sats — no price feed involved for that comparison — set the sats-native equivalents alongside or instead of the USD ones: ```json { "limits": { "maxPerPaymentSats": 50000, "maxPerSessionSats": 200000 }, "tiers": { "autoApproveSats": 1000 } } ``` Whichever pair is present for a given check wins for that check; the two are not summed. The system as a whole still **fails closed** if the BTC price feed is unavailable **for any USD-denominated threshold that needs conversion** — a sats-native limit has nothing to convert, so it is unaffected by a price-feed outage, but a payment gated by a USD tier is refused (not approved) while the feed is down. `budget(action="tighten")` (see [above](#budget)) accepts `maxPerPaymentSats` / `maxPerSessionSats` for runtime tightening regardless of which pair the operator configured. ### Out-of-Band Confirmation When a payment exceeds the auto-approve threshold, the server delivers a confirmation code over one of four channels, set with `confirmation.channel` in the config file: | `confirmation.channel` | Where the code goes | Use it when | |---|---|---| | `stderr` *(default)* | The server's console/stderr — the channel a human operator watching the terminal sees | Interactive local use — Claude Code, Claude Desktop, a terminal session | | `refuse` | Nowhere — the payment is refused outright rather than printed anywhere | Non-interactive / hosted contexts where nothing reads stderr, so printing a code there would be silently lost (or worse, readable by the wrong process) | | `webhook` | POSTed to a configured webhook URL | A human is notified out-of-band (chat app, pager) rather than watching a terminal | | `file` | Written to a local file the human can read on their own schedule | Headless or scheduled runs where no one is watching in real time | `LIGHTNING_ENABLE_HOSTED=1` changes the *default*: when set, and the process is not attached to a TTY, the channel defaults to `refuse` instead of `stderr` — because in a hosted, non-interactive context there is no guarantee a human (rather than the very agent process, or something it can read) is the one watching stderr. Set `confirmation.channel` explicitly to opt back into `webhook` or `file` in that environment. In every case the code is **never returned in a tool result**, so a prompt-injected agent can't read its own code and self-approve. The flow (channels other than `refuse`): 1. The agent calls the payment tool (`pay_invoice`, `access_l402_resource`, `pay_l402_challenge`, or `wallet_ops` with `action="send_onchain"`); the response says confirmation is required — without the code. 2. The server delivers the code over the configured channel, where you read it. 3. You give the code to the AI, which re-calls the **original** payment tool with the `confirmationNonce` (.NET) / `confirmation_nonce` (Python) parameter. The separate `verify_confirmation_code` tool only *verifies* a code — it never executes a payment. Codes are bound to the exact **amount, tool, and destination** (invoice / URL / on-chain address) — a code can't be reused for a different payment or redirected to a different destination (v1.12.13). `wallet_ops` with `action="send_onchain"` **always** requires a code (irreversible) and fails closed if the budget service is unavailable. ### Why This is AI-Proof - Config file lives in your home directory, not environment variables - The only budget tool, `budget`, is **tighten-only** for `action="tighten"` — it can lower the runtime sats caps but can never raise any limit above your config file - Only you can edit the config file - Payments above the auto-approve threshold require the out-of-band confirmation code that only the human (or the channel you configured for them) can see — and in a hosted/non-TTY context, `LIGHTNING_ENABLE_HOSTED=1` refuses by default rather than guessing at a safe channel --- ## Environment Variables Reference | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `STRIKE_API_KEY` | If using Strike | - | Strike API key | | `OPENNODE_API_KEY` | If using OpenNode | - | OpenNode API key | | `OPENNODE_ENVIRONMENT` | No | production | `production` or `dev` | | `NWC_CONNECTION_STRING` | If using NWC | - | Nostr Wallet Connect URI | | `LND_REST_HOST` | If using LND | - | LND REST API host | | `LND_MACAROON_HEX` | If using LND | - | LND admin macaroon in hex | | `LND_SKIP_TLS_VERIFY` | No | false | Set `true` to skip LND TLS verification (development only) | | `LND_TLS_CERT_PATH` | No | - | Path to LND `tls.cert` for remote connections (.NET package) | | `WALLET_PRIORITY` | No | lnd > nwc > strike > opennode | Force a specific wallet when several are configured (e.g., `nwc`) | | `LIGHTNING_ENABLE_API_KEY` | For `l402_producer` + some `agent_services` actions | - | Lightning Enable API key — required by every `l402_producer` action, and by the `agent_services` actions `request`/`publish`/`unpublish`/`attest` (`discover`/`settle`/`reputation` work without it). No key? `create_lightning_enable_account` provisions one for ~100 sats over Lightning | | `LIGHTNING_ENABLE_API_URL` | No | https://api.lightningenable.com | API base URL | | `L402_REGISTRY_URL` | No | falls back to `LIGHTNING_ENABLE_API_URL` | Registry endpoint used by `discover_api` | | `LIGHTNING_ENABLE_TOOL_PROFILE` | No | `standard` | `lite`, `standard`, or `full` — see [Tool Profiles](#tool-profiles) | | `LIGHTNING_ENABLE_HOSTED` | No | unset | Set to `1` in a non-interactive/hosted deployment — changes the out-of-band confirmation default to `refuse` when the process isn't attached to a TTY. See [Out-of-Band Confirmation](#out-of-band-confirmation) | :::note L402 Tools L402 tools are free and don't require an API key or license purchase. Just configure a compatible wallet and all L402 tools are available immediately. ::: --- ## Security Best Practices 1. **Use a dedicated wallet** - Never use your main wallet or business funds for AI spending 2. **Configure budget limits** - Edit `~/.lightning-enable/config.json` before first use 3. **Review payment history** - Check `receipts` (with `source="session"` or `source="durable"`) after sessions 4. **Rotate API keys** - Monthly rotation recommended 5. **Never commit keys** - Keep API keys out of version control 6. **Preimage log safety** - Preimage values are automatically truncated in logs (first 8 characters only), preventing full preimage exposure in log files or debug output See [AI Spending Security](/products/agentic-commerce/ai-spending-security) for detailed security guidance. --- ## Troubleshooting ### "Wallet not configured" Set one of: `STRIKE_API_KEY`, `NWC_CONNECTION_STRING`, `LND_REST_HOST`+`LND_MACAROON_HEX`, or `OPENNODE_API_KEY` ### "Budget limit exceeded" Payment exceeds your limits. Use `budget` with `action="status"` to see current limits, edit config file to adjust. ### "Tool not found" after upgrading If a prompt or integration calls an old name like `configure_budget` or `create_l402_challenge` directly, either switch it to the new consolidated tool (`budget(action="tighten")`, `l402_producer(action="create")`, …) or set `LIGHTNING_ENABLE_TOOL_PROFILE=full` to keep every legacy name available as a deprecated alias until v3.0.0. See [Tool Profiles](#tool-profiles) and [Deprecated aliases](#deprecated-aliases). ### "License required for L402 features" (pre-v1.6.0 only) This error only occurs on versions before v1.6.0. All L402 consumer tools are free in v1.6.0 and later. If you see this error, update your MCP server to the latest version. ### "Feature not supported" Some features are wallet-specific (e.g., BTC price is Strike-only). Check the compatibility table above. ### "L402 payment succeeded but access failed" Your wallet doesn't return preimage. Use **LND**, **CoinOS**, **CLINK**, **Alby Hub**, or **Strike**. OpenNode and Primal don't work for L402 (no preimage return). --- ## Try It: Lightning Enable Store The [Lightning Enable Store](https://store.lightningenable.com) is a live L402-powered web store where AI agents can purchase physical merchandise using Bitcoin Lightning payments. ``` Ask Claude: "Buy me a Lightning Enable t-shirt from store.lightningenable.com" ``` This demonstrates the full L402 flow: 1. **Browse catalog** — `GET /api/store/catalog` 2. **Checkout** — `POST /api/store/checkout` (returns HTTP 402 with invoice + macaroon) 3. **Pay invoice** — Use `pay_invoice` with the BOLT11 string 4. **Claim order** — `POST /api/store/claim` with L402 credential --- ## Next Steps - [L402 Producer API](/products/agentic-commerce/l402-producer-api) - **Make your agents earn** with L402 challenges - [MCP Wallet Setup](/products/agentic-commerce/mcp-wallet-setup) - Detailed wallet configuration - [AI Spending Security](/products/agentic-commerce/ai-spending-security) - Budget configuration - [AI Agent Integration](/products/agentic-commerce/ai-agent-integration) - Advanced usage ============================================================================== # MCP Quick Start Source: https://docs.lightningenable.com/products/agentic-commerce/mcp-quickstart ============================================================================== # Lightning MCP: 3-Minute Setup Get your AI paying Lightning invoices in under 3 minutes. The MCP server is open-source (MIT) and free to install. Wallet, invoice, L402, budget, and API-discovery tools work out of the box with just a wallet — no API key required. Producer tools (sell access via L402) and Agent Service Agreement tools (agent-to-agent commerce over Nostr) unlock with a Lightning Enable API key. See the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the full tool list. :::tip What You'll Get After this guide, Claude can: - Pay any Lightning invoice - Access L402-protected APIs with automatic micropayments - Check your wallet balance and payment history - Buy from L402 stores (like [agent-commerce.store](https://agent-commerce.store)) ::: --- ## Try L402 Now (No Setup Required) Before installing anything, you can verify L402 works with a single curl: ```bash curl https://api.lightningenable.com/l402/test/ping ``` You'll get a `402 Payment Required` response with a 1-sat Lightning invoice. Pay the invoice with any Lightning wallet, then retry with the L402 token: ```bash curl -H "Authorization: L402 :" \ https://api.lightningenable.com/l402/test/ping ``` You'll get a `200 OK` confirming L402 is working. No API key or signup required. --- ## What You Need 1. **Claude Code or Claude Desktop** — Already installed 2. **A Strike account** — Free, 2-minute signup --- ## Step 1: Get a Strike API Key (1 minute) [Strike](https://strike.me) is the fastest way to start. No infrastructure, no node, full L402 support. 1. Download the Strike app or go to [strike.me](https://strike.me) 2. Create an account (requires phone number) 3. Go to [developer.strike.me](https://developer.strike.me) 4. Log in with your Strike account 5. Click **Generate API Key** 6. Copy the API key :::tip Why Strike? Strike requires zero infrastructure, supports L402 (returns preimage on every payment), and handles custody. Want alternatives? See [Wallet Options](/products/agentic-commerce/mcp-wallet-setup) for NWC wallets (CoinOS, Alby Hub, CLINK) or [LND](/products/agentic-commerce/lnd-setup) for self-hosted. ::: ### Fund Your Wallet Send a small amount of Bitcoin to your Strike wallet, or buy directly in the Strike app. Start with $1-5 worth. --- ## Step 2: Install the MCP Server (30 seconds) ```bash # .NET (recommended) dotnet tool install -g LightningEnable.Mcp # Or Python pip install lightning-enable-mcp ``` :::info .NET not installed? Download the .NET SDK from https://dot.net/download (the tool runs on .NET 8, 9, or 10) ::: :::note NWC wallets The Python package installs on every platform (including Windows) with no build toolchain. If you connect a **Nostr Wallet Connect (NWC)** wallet, install the optional extra instead: `pip install lightning-enable-mcp[nwc]`. Other wallet types (LND, Strike, OpenNode) don't need it. (.NET is unaffected.) ::: --- ## Step 3: Configure Claude (1 minute) Claude Code and Claude Desktop are configured differently — pick the one you use. ### Claude Code (CLI) Register the server with the `claude mcp add` command: **.NET:** ```bash claude mcp add --transport stdio lightning-enable \ --env STRIKE_API_KEY=your-strike-api-key \ -- lightning-enable-mcp ``` **Python:** ```bash claude mcp add --transport stdio lightning-enable \ --env STRIKE_API_KEY=your-strike-api-key \ -- uvx lightning-enable-mcp ``` Alternatively, for a project-scoped setup, create a `.mcp.json` file in your project root (don't commit API keys): ```json { "mcpServers": { "lightning-enable": { "command": "lightning-enable-mcp", "env": { "STRIKE_API_KEY": "your-strike-api-key" } } } } ``` Restart Claude Code (or run `/mcp` to check the server is connected). ### Claude Desktop Edit the Claude Desktop config file: **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` **Linux:** `~/.config/claude/claude_desktop_config.json` **.NET:** ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "STRIKE_API_KEY": "your-strike-api-key" } } } } ``` **Python:** ```json { "mcpServers": { "lightning-enable": { "command": "uvx", "args": ["lightning-enable-mcp"], "env": { "STRIKE_API_KEY": "your-strike-api-key" } } } } ``` Restart Claude Desktop after saving. ### Other Clients - [Cursor Setup](/products/agentic-commerce/cursor-setup) — `.cursor/mcp.json` - [Claude Code Setup](/products/agentic-commerce/claude-code-setup) — Full Claude Code walkthrough --- ## Step 4: Test It! Open Claude and try: ### Check Your Balance ``` Check my Lightning balance ``` ### Pay an Invoice ``` Pay this Lightning invoice: lnbc... ``` ### Access an L402 API ``` Get weather data for New York from agent-commerce.store ``` Claude will automatically handle the L402 payment and return the data. ### Buy from the Lightning Enable Store ``` Buy me a Lightning Enable t-shirt from store.lightningenable.com ``` Claude browses the catalog, checks out, pays the invoice, and gives you a claim link. --- ## You're Done! The MCP server is open-source (MIT) and free to install. Wallet, invoice, L402, budget, and API-discovery tools work out of the box with just a wallet. Producer tools (sell access via L402) and Agent Service Agreement tools (agent-to-agent commerce over Nostr) unlock with a Lightning Enable API key. See the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the full tool list. --- ## Set Spending Limits Protect yourself with budget controls. Create `~/.lightning-enable/config.json`: ```json { "currency": "USD", "tiers": { "autoApprove": 0.10, "logAndApprove": 1.00, "formConfirm": 10.00 }, "limits": { "maxPerPayment": 50.00, "maxPerSession": 20.00 } } ``` | Threshold | Behavior | |-----------|----------| | Under $0.10 | Auto-pay silently | | $0.10 - $1.00 | Pay and log | | $1.00 - $50.00 | Confirmation required — the server prints a code to its console; you give the code to the AI to approve | | Over $50/payment | Denied | | Over $20/session | Denied | --- ## Troubleshooting ### "Wallet not configured" Your Strike API key isn't being read. Check: 1. Environment variable is set correctly in your MCP config 2. Restart Claude after saving config changes 3. Try running `lightning-enable-mcp` directly to see errors ### "dotnet: command not found" Install the .NET SDK (8, 9, or 10) from https://dot.net/download ### "uvx: command not found" Install uv: `pip install uv` or `curl -LsSf https://astral.sh/uv/install.sh | sh` --- ## Next Steps - [Spending Security](/products/agentic-commerce/ai-spending-security) — Advanced budget controls - [Wallet Options](/products/agentic-commerce/mcp-wallet-setup) — NWC, LND, and other wallets - [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) — full tool reference - [Agent Commerce Store](https://agent-commerce.store) — dozens of L402 APIs to test with --- ## Quick Reference | Setting | Where | |---------|-------| | Strike API Key | [developer.strike.me](https://developer.strike.me) | | Claude Code Config | `claude mcp add ...` or project `.mcp.json` | | Claude Desktop Config | `claude_desktop_config.json` (per-OS paths above) | | Budget Config | `~/.lightning-enable/config.json` | | MCP Install | `dotnet tool install -g LightningEnable.Mcp` | **Questions?** [Open an issue on GitHub](https://github.com/refined-element/lightning-enable-mcp/issues). ============================================================================== # Wallet Configuration Source: https://docs.lightningenable.com/products/agentic-commerce/mcp-wallet-setup ============================================================================== # MCP Wallet Setup This guide covers all wallet options for the Lightning Enable MCP server. **The critical factor for L402 is whether the wallet returns preimage** - without it, L402 authentication fails. ## L402 Compatibility Matrix | Wallet | Returns Preimage | L402 Works | Custody | Best For | |--------|-----------------|------------|---------|----------| | **LND (Your Node)** | ✅ Always | ✅ Yes | You control | Power users, guaranteed L402 | | **NWC (CoinOS)** | ✅ Yes | ✅ Yes | Custodial | Free, easy, L402 works | | **NWC (CLINK)** | ✅ Yes | ✅ Yes | Custodial | Nostr users, L402 works | | **Alby** | ✅ Yes | ✅ Yes | You hold the keys | NWC compatible | | **Strike** | ✅ Yes | ✅ Yes | Custodial | USD users, easy setup, L402 works | | **Primal** | ❌ No | ❌ No | Custodial | Direct payments only | | **OpenNode** | ❌ No | ❌ No | Custodial | Direct payments only | :::warning Building on L402? Wallet preimage return is critical. If your wallet doesn't return preimages, L402 authentication breaks - payment succeeds but API access fails. ::: :::danger Why Preimage Matters L402 authentication requires the preimage (proof of payment) to create credentials. If your wallet doesn't return it, payment succeeds but API access fails. ::: ## Recommended Setup for L402 ### Option 1: LND (Best for Guaranteed L402) **Why LND?** - You run your own Lightning node - LND **always** returns preimage - L402 is guaranteed to work - No third-party dependency - You run the node — you hold the keys **Requirements:** - Running LND node (local or remote) - REST API enabled - Admin macaroon **Configuration:** ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "LND_REST_HOST": "localhost:8080", "LND_MACAROON_HEX": "your-admin-macaroon-in-hex" } } } } ``` **Getting Your Macaroon in Hex:** ```bash # Linux/Mac: xxd -ps -c 1000 ~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon # Windows PowerShell: [System.BitConverter]::ToString([System.IO.File]::ReadAllBytes("$env:LOCALAPPDATA\Lnd\data\chain\bitcoin\mainnet\admin.macaroon")) -replace '-','' ``` **Or use config file** (`~/.lightning-enable/config.json`): ```json { "wallets": { "lndRestHost": "localhost:8080", "lndMacaroonHex": "0201036c6e6402f801...", "priority": "lnd" } } ``` ### Option 2: NWC with CoinOS (Free, Easy) **Why CoinOS?** - Completely free - Browser-based, no install - Returns preimage - L402 works - Easy to set up :::info NWC Connection Timeouts The MCP server uses tuned timeouts for NWC WebSocket connections: **3 seconds** for the NIP-47 INFO encryption auto-detect, then roughly **30 seconds** (.NET) or **60 seconds** (Python) waiting for a payment response. If your NWC relay is slow or unreachable, the connection fails gracefully after these timeouts rather than hanging indefinitely. ::: :::note Python install for NWC If you run the **Python** package with an NWC wallet, install the optional extra: `pip install lightning-enable-mcp[nwc]`. The base `pip install lightning-enable-mcp` works on every platform (including Windows) but raises a clear error if you connect NWC without the extra. (.NET needs nothing extra.) ::: **Setup:** 1. Sign up at https://coinos.io 2. Go to Settings → NWC 3. Create a new NWC connection 4. Enable **auto-pay** for the connection (important for L402!) 5. Copy the connection string ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "NWC_CONNECTION_STRING": "nostr+walletconnect://..." } } } } ``` :::tip Windows Users If the connection string contains special characters that don't work in environment variables, use the config file instead: ```json { "wallets": { "nwcConnectionString": "nostr+walletconnect://...", "priority": "nwc" } } ``` ::: ### Option 3: NWC with Alby Hub (You Hold the Keys) **Why Alby Hub?** - You hold the keys — you control the funds - Returns preimage - L402 works - Connect to your own node **Setup:** 1. Deploy Alby Hub: https://albyhub.com?ref=magma 2. Connect to your Lightning node 3. Create NWC connection 4. Use same configuration as CoinOS above --- ### Option 4: Strike (Easy Setup, L402 Works) **Why Strike?** - Easy API key setup - USD and BTC balance management - Returns preimage via `lightning.preImage` - **L402 works** - Built-in currency exchange **Setup:** 1. Create an account at https://strike.me 2. Get your API key from https://dashboard.strike.me 3. Fund your Strike account with BTC ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "STRIKE_API_KEY": "your-strike-api-key" } } } } ``` :::tip Strike BTC Balance Strike payments use your BTC balance by default. Make sure you have BTC funded (not just USD) for Lightning payments. ::: :::info Strike Payment Polling When paying an invoice through Strike, the MCP server polls for payment completion using **exponential backoff**: 1s, 1s, 1s, 2s, 2s, 2s, then capped at 4s intervals. This reduces unnecessary API calls while still detecting payment completion quickly. ::: **Available Tools (all work):** - `pay_invoice` ✅ - `get_balance` ✅ (multi-currency) - `wallet_ops` with `action="price"` ✅ (formerly `get_btc_price`) - `wallet_ops` with `action="exchange"` ✅ (formerly `exchange_currency`) - `wallet_ops` with `action="send_onchain"` ✅ (formerly `send_onchain`) - `access_l402_resource` ✅ (preimage returned) --- ## Wallets That Do NOT Support L402 ### OpenNode OpenNode is a payment software provider but **does not return preimage**. **Use for:** Direct payments, creating invoices **Cannot use for:** L402 auto-pay ```json { "env": { "OPENNODE_API_KEY": "your-opennode-api-key", "OPENNODE_ENVIRONMENT": "production" } } ``` --- ## Wallet Priority If multiple wallets are configured, they're used in this order (optimized for L402): 1. **LND** (if `LND_REST_HOST` + `LND_MACAROON_HEX` are set) 2. **NWC** (if `NWC_CONNECTION_STRING` is set) 3. **Strike** (if `STRIKE_API_KEY` is set) 4. **OpenNode** (if `OPENNODE_API_KEY` is set) Override with `WALLET_PRIORITY` environment variable or config file: ```json { "wallets": { "priority": "nwc" } } ``` --- ## Using Config File for Credentials Environment variables can be tricky (especially on Windows with special characters). The config file at `~/.lightning-enable/config.json` is often easier: ```json { "currency": "USD", "tiers": { "autoApprove": 0.10, "logAndApprove": 1.00, "formConfirm": 10.00, "urlConfirm": 100.00 }, "limits": { "maxPerPayment": 500.00, "maxPerSession": 100.00 }, "session": { "requireApprovalForFirstPayment": false, "cooldownSeconds": 2 }, "wallets": { "lndRestHost": "localhost:8080", "lndMacaroonHex": "0201036c6e64...", "nwcConnectionString": "nostr+walletconnect://...", "strikeApiKey": "your-strike-key", "openNodeApiKey": "your-opennode-key", "priority": "lnd" } } ``` --- ## Complete Examples ### LND + Full L402 Support ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "LND_REST_HOST": "localhost:8080", "LND_MACAROON_HEX": "0201036c6e6402f801..." } } } } ``` L402 tools are available immediately - no license purchase needed. ### CoinOS NWC + L402 Support ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "NWC_CONNECTION_STRING": "nostr+walletconnect://abc...?relay=wss://relay.coinos.io&secret=xyz..." } } } } ``` L402 tools are available immediately - no license purchase needed. ### Strike + Full L402 Support ```json { "mcpServers": { "lightning-enable": { "command": "dotnet", "args": ["tool", "run", "lightning-enable-mcp"], "env": { "STRIKE_API_KEY": "your-strike-api-key" } } } } ``` L402 tools are available immediately - no license purchase needed. --- ## Tool Availability The MCP server is open-source (MIT) and free to install. Wallet, invoice, L402, budget, and API-discovery tools work out of the box with just a wallet configured. Producer tools (sell access via L402) and Agent Service Agreement tools (agent-to-agent commerce over Nostr) unlock with a Lightning Enable API key. See the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the full tool list, including per-wallet compatibility notes. --- ## Troubleshooting ### "L402 payment succeeded but access failed" Your wallet doesn't return preimage. Solutions: 1. **Best:** Switch to LND (run your own node) 2. **Easy:** Switch to NWC with CoinOS or Alby 3. **Also works:** Strike (returns preimage) 4. OpenNode will NOT work for L402 ### "temporary_channel_failure" or routing errors Lightning network routing issue. Try: - Wait a few minutes and retry - Ensure your wallet has sufficient balance - Check if the destination node is online ### "Wallet not configured" No wallet credentials found. Set one of: - `LND_REST_HOST` + `LND_MACAROON_HEX` - `NWC_CONNECTION_STRING` - `STRIKE_API_KEY` - `OPENNODE_API_KEY` Or use the config file at `~/.lightning-enable/config.json` ### NWC "Payment cancelled by user" Enable auto-pay in your NWC wallet: - CoinOS: Settings → NWC → Edit connection → Enable auto-pay - Alby: Check connection permissions Also set in config: ```json { "session": { "requireApprovalForFirstPayment": false } } ``` ### "License required for L402 features" (pre-v1.6.0 only) This error only occurs on versions before v1.6.0. All L402 consumer tools are free and included with the MCP server in v1.6.0 and later. If you see this error, update your MCP server: ```bash dotnet tool update -g LightningEnable.Mcp ``` Note: `l402_producer` (every action — formerly the separate `create_l402_challenge` / `verify_l402_payment` tools) and the `agent_services` actions `request`/`publish`/`unpublish`/`attest` (formerly `request_agent_service` / `publish_agent_capability` / `unpublish_agent_capability` / `publish_agent_attestation`) do require a Lightning Enable API key via `LIGHTNING_ENABLE_API_KEY` — this is by design, not a bug. The out-of-the-box tools, plus `agent_services`'s `discover`/`settle`/`reputation` actions, work with just a wallet. --- ## Security Notes - **Config file cannot be modified through any MCP tool** - your spending limits are protected (an agent with direct shell or filesystem access to the host is outside this guarantee) - **Macaroons should be kept secret** - treat them like passwords - **Use read-only macaroons** if you only need balance checks - **Preimage values are truncated in logs** - only the first 8 characters are logged for debugging purposes, preventing full preimage exposure in log files --- ## Next Steps - [AI Agent Integration](/products/agentic-commerce/ai-agent-integration) - Full MCP tool reference - [AI Spending Security](/products/agentic-commerce/ai-spending-security) - Budget configuration - [How It Works](/products/agentic-commerce/how-it-works) - L402 protocol details ============================================================================== # Native Integration — ASP.NET Core Source: https://docs.lightningenable.com/products/agentic-commerce/native-integration-aspnet ============================================================================== # Native L402 Integration — ASP.NET Core This walkthrough takes you from a vanilla ASP.NET Core app to charging Lightning payments per request in under 10 minutes. You'll install [`L402Server.AspNetCore`](https://www.nuget.org/packages/L402Server.AspNetCore), add one line of middleware + an attribute, and your existing endpoints become paid endpoints. > If you haven't picked an integration mode yet, start with the [Native Integration overview](./native-integration) to understand when Native mode is the right fit. :::tip Runnable example A complete, working ASP.NET Core integration lives at [`l402-example-aspnet`](https://github.com/refined-element/l402-example-aspnet) — clone it, set your API key, and curl a live 402 → pay → 200 flow before wiring your own app. ::: ## Prerequisites - .NET 8.0 or higher - An ASP.NET Core app - A Lightning Enable merchant API key — generate at **Dashboard → Settings → API Keys** - A payment provider (Strike or OpenNode) configured under **Dashboard → Settings → Payment Provider** ## Install ```bash dotnet add package L402Server.AspNetCore ``` (`L402Server` — the underlying SDK — is pulled in transitively. Both are MIT-licensed.) ## 30-second example ```csharp using L402Server.AspNetCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddL402AspNetCore(opts => { opts.ApiKey = builder.Configuration["LightningEnable:ApiKey"]!; }); var app = builder.Build(); app.UseRouting(); app.UseL402(); app.MapControllers(); app.Run(); ``` Then mark any controller action with `[L402(PriceSats = N)]`: ```csharp [ApiController] [Route("api/premium")] public class PremiumController : ControllerBase { [HttpGet("weather")] [L402(PriceSats = 100)] public IActionResult Weather() => Ok(new { temp = 72 }); } ``` That's the whole integration. The middleware: 1. Reads `Authorization: L402 :` from each request 2. If the matched endpoint has `[L402]` and no valid credential → mints a fresh challenge via Lightning Enable's hosted producer API and returns `402 Payment Required` 3. If a valid credential is present → executes the action Endpoints without `[L402]` pass through ungated. :::warning L402Server.AspNetCore 0.1.x verifies macaroon + preimage only — assert the resource yourself when gating multiple tiers Current versions (`L402Server.AspNetCore` 0.1.x, `L402Server` 0.1.x) call the verify endpoint with **only the macaroon and preimage** — they do not pass or compare the request path or amount. Merchant binding and expiry are always enforced by the hosted API, but a token bought for one of your endpoints will also pass verification on a *different* endpoint gated under the same API key, within the token's validity window (60 minutes by default). If you gate **multiple prices or resources** under one API key, add a resource assertion in your action: ```csharp [HttpGet("weather"), L402(PriceSats = 100)] public IActionResult Weather() { var result = (VerificationResult)HttpContext.Items[L402HttpContextKeys.VerificationResult]!; if (result.Resource != HttpContext.Request.Path) { return Unauthorized(new { error = "Token was purchased for a different resource" }); } return Ok(new { temp = 72 }); } ``` An SDK update that passes the request path through to the verify call automatically is in progress; this applies to the 0.1.x releases. A single flat price across your gated surface is unaffected in practice — every token you issue is for the same class of access. ::: ## What the caller sees ### Without payment ```bash curl -i https://your-api.example/api/premium/weather ``` ```http HTTP/1.1 402 Payment Required Content-Type: application/json WWW-Authenticate: L402 macaroon="AgELbWFjYXJvb24...", invoice="lnbc1u1p3..." { "error": "Payment Required", "l402": { "macaroon": "AgELbWFjYXJvb24...", "invoice": "lnbc1u1p3...", "amount_sats": 100, "payment_hash": "abc123...", "expires_at": "2026-05-12T01:00:00Z", "resource": "/api/premium/weather" } } ``` ### With payment ```bash curl -i https://your-api.example/api/premium/weather \ -H 'Authorization: L402 AgELbWFjYXJvb24...:deadbeef...' ``` ```http HTTP/1.1 200 OK Content-Type: application/json { "temp": 72 } ``` ## Pricing patterns ### Per-route attributes (compile-time prices) ```csharp [HttpGet("forecast"), L402(PriceSats = 100)] public IActionResult Forecast() => Ok(...); [HttpGet("premium-llm"), L402(PriceSats = 500, Description = "GPT-4 backed")] public IActionResult PremiumLlm() => Ok(...); ``` ### Global flat price (gates everything mounted under the middleware) ```csharp app.UseL402(opts => opts.DefaultPriceSats = 100); ``` ### Function-form pricing (variable per request) `PriceSelector` is a `Func>?` — note the nullable `int?`: ```csharp app.UseL402(opts => { opts.PriceSelector = ctx => ValueTask.FromResult( ctx.Request.Query["model"] == "premium" ? 500 : 100); }); ``` Resolution order: 1. `PriceSelector` — consulted first if set. Returning a non-null value decides the price for that request. Returning `null` means "no opinion" and falls through to the next step — it does **not** ungate the request. 2. `[L402(PriceSats = N)]` attribute on the matched endpoint 3. `DefaultPriceSats` on options 4. All three unset/null → request passes through ungated This lets you use `PriceSelector` for just the requests it recognizes (say, a surge-priced model parameter) while attributes and `DefaultPriceSats` cover everything else. ## The `[L402]` attribute in full `L402Attribute` has three properties and can target methods **or classes**: | Property | Type | Notes | |---|---|---| | `PriceSats` | `int` (required) | Price in satoshis, ≥ 1. Compile-time constant — for variable pricing use `PriceSelector` on the options. | | `Description` | `string?` | Embedded in the Lightning invoice; shown in the payer's wallet UI. | | `Resource` | `string?` | Optional override for the resource bound into the macaroon's path caveat. Defaults to the request path when omitted. | ### Class-level gating Apply `[L402]` to a controller class to gate every action on it at one price: ```csharp [ApiController] [Route("api/premium")] [L402(PriceSats = 100)] // every action on this controller costs 100 sats public class PremiumController : ControllerBase { [HttpGet("weather")] public IActionResult Weather() => Ok(new { temp = 72 }); [HttpGet("forecast")] public IActionResult Forecast() => Ok(new { days = 7 }); } ``` A method-level `[L402]` on an action takes precedence over a class-level one for that action. ### Resource override ```csharp [HttpGet("weather")] [L402(PriceSats = 100, Resource = "/api/premium/weather-v1")] public IActionResult Weather() => Ok(new { temp = 72 }); ``` The resource bound into the challenge resolves in this order: 1. `Resource` on the matched `[L402]` attribute 2. `ResourceSelector` on the middleware options 3. `HttpContext.Request.Path` (the default) ## Configuration reference `L402AspNetCoreOptions`: | Option | Type | Default | Notes | |---|---|---|---| | `DefaultPriceSats` | `int?` | `null` | Flat price applied when no `[L402]` attribute is present | | `PriceSelector` | `Func>?` | `null` | Variable pricing per request. A non-null return wins; `null` falls through to the `[L402]` attribute, then `DefaultPriceSats` | | `ResourceSelector` | `Func?` | `HttpContext.Request.Path` | Bound as a macaroon caveat | | `DescriptionSelector` | `Func?` | `null` | Shown in the payer's Lightning wallet | | `IdempotencyKeySelector` | `Func?` | `null` | Sends `X-Idempotency-Key` for retry-safe challenge minting | | `OnInvalidToken` | `Func?` | sends `401` | Custom handler | ## Accessing the verified credential in your action After a successful verification the middleware sets `HttpContext.Items[L402HttpContextKeys.VerificationResult]`. In a controller, read it via the `HttpContext` property that `ControllerBase` exposes (MVC does not bind `HttpContext` as an action **parameter** — a `Weather(HttpContext ctx)` signature won't be populated): ```csharp [HttpGet("weather"), L402(PriceSats = 100)] public IActionResult Weather() { var result = (VerificationResult)HttpContext.Items[L402HttpContextKeys.VerificationResult]!; _logger.LogInformation( "Served {Resource} for {Sats} sats ({Hash})", result.Resource, result.AmountSats, result.PaymentHash); return Ok(new { temp = 72 }); } ``` (In minimal APIs, an `HttpContext` handler parameter *is* injected — the property route is only needed inside controllers.) Useful for usage logging, per-endpoint analytics, fraud detection. ## Minimal API support `[L402]` is a regular `Attribute` so it works on minimal-API metadata too: ```csharp app.MapGet("/api/premium/weather", () => new { temp = 72 }) .WithMetadata(new L402Attribute { PriceSats = 100 }); ``` ## Using the SDK directly (without the middleware) If you need to mint a challenge or verify a token outside the request pipeline — from a background service, a hosted worker, an HTTP handler in a non-ASP.NET context — use `L402Server` directly: ```csharp using L402Server; var client = new L402ServerClient(new L402ServerOptions { ApiKey = Environment.GetEnvironmentVariable("LIGHTNING_ENABLE_API_KEY")!, }); var challenge = await client.CreateChallengeAsync(new CreateChallengeRequest { Resource = "/api/x", PriceSats = 100, }); var verification = await client.VerifyTokenAsync(new VerifyTokenRequest { Macaroon = mac, Preimage = pre, }); ``` ## Pipeline order The middleware needs to be placed AFTER `UseRouting()` (so it can read `[L402]` attribute metadata from the matched endpoint) and BEFORE the endpoint executor (`MapControllers`, `UseEndpoints`, etc.): ```csharp app.UseRouting(); app.UseAuthentication(); // any other auth middleware app.UseAuthorization(); app.UseL402(); // ← here app.MapControllers(); ``` If you put `UseL402()` before `UseRouting()` the middleware will see no matched endpoint and won't find `[L402]` attributes. If you put it after `MapControllers()` it'll never run. ## Custom failure handling Default behavior: invalid L402 token → `401 Unauthorized`. Override with `OnInvalidToken` to send a fresh `402` instead: ```csharp app.UseL402(opts => { opts.OnInvalidToken = async (ctx, failure) => { ctx.Response.StatusCode = 402; await ctx.Response.WriteAsJsonAsync(new { error = "Token rejected — please pay again", details = failure.Error, }); }; }); ``` When `OnInvalidToken` is supplied, the middleware does NOT send the default 401 and does NOT continue to the next middleware — your callback is fully responsible for producing the response. Write a status code + body via `ctx.Response`, or redirect via `ctx.Response.Redirect(...)`. The pipeline short-circuits after your callback returns. (The callback signature is `Func` — there's no `next` delegate passed in.) ## Troubleshooting ### `502 Bad Gateway` on every request The middleware couldn't reach Lightning Enable. Check: - `LIGHTNING_ENABLE_API_KEY` is set and valid - No outbound firewall blocking `api.lightningenable.com` - Subscription is active ### Every request returns 402 even after payment Behind a reverse proxy? Confirm the `Authorization` header is being forwarded to the ASP.NET Core app. In Azure App Service and most reverse-proxy setups this just works; some custom proxies strip the header. ### `403 Forbidden` from upstream L402 isn't enabled on your subscription plan. Check **Dashboard → Settings → Plan** — Native mode requires Agentic Commerce or Agentic Commerce — Business. ### `[L402]` attributes don't seem to work Verify `app.UseL402()` is placed AFTER `app.UseRouting()` and BEFORE `app.MapControllers()` / `app.UseEndpoints()`. The middleware reads attribute metadata from the matched endpoint, which only exists after routing has run. ## Source and license Both packages are MIT-licensed open source: - [`L402Server.AspNetCore` on GitHub](https://github.com/refined-element/le-server-l402-aspnetcore-dotnet) - [`L402Server` on GitHub](https://github.com/refined-element/le-server-l402-dotnet) ## Next steps - [Producer API reference](./producer-api-reference) — full HTTP surface - [Native Integration — Express](./native-integration-express) — Node + Express version of this walkthrough - [Proxy setup walkthrough](./proxy-setup-walkthrough) — if Proxy mode is a better fit ============================================================================== # Native Integration — Express (Node.js) Source: https://docs.lightningenable.com/products/agentic-commerce/native-integration-express ============================================================================== # Native L402 Integration — Express (Node.js) This walkthrough takes you from a vanilla Express app to charging Lightning payments per request in under 10 minutes. You'll install [`l402-express`](https://www.npmjs.com/package/l402-express), add one line of middleware, and your existing endpoints become paid endpoints. > If you haven't picked an integration mode yet, start with the [Native Integration overview](./native-integration) to understand when Native mode is the right fit. :::tip Runnable example A complete, working Express integration lives at [`l402-example-node`](https://github.com/refined-element/l402-example-node) — clone it, set your API key, and curl a live 402 → pay → 200 flow before wiring your own app. ::: ## Prerequisites - Node.js 18 or higher - An Express app (4.x or 5.x) - A Lightning Enable merchant API key — generate at **Dashboard → Settings → API Keys** - A payment provider (Strike or OpenNode) configured under **Dashboard → Settings → Payment Provider** ## Install ```bash npm install l402-express l402-server ``` `l402-server` is the underlying SDK that `l402-express` calls; both are MIT-licensed and the middleware re-exports the SDK's types so most consumers won't import `l402-server` directly. ## 30-second example ```js import express from "express"; import { l402 } from "l402-express"; const app = express(); // Anything mounted here costs 100 sats per request. app.use("/api/premium", l402({ apiKey: process.env.LIGHTNING_ENABLE_API_KEY, priceSats: 100, })); app.get("/api/premium/weather", (_req, res) => { res.json({ temp: 72 }); }); app.listen(3000); ``` That's the whole integration. The middleware: 1. Reads `Authorization: L402 :` from each request 2. If absent → mints a fresh challenge via Lightning Enable's hosted producer API and returns `402 Payment Required` with the invoice in the `WWW-Authenticate` header and a JSON body 3. If present → verifies the credential via Lightning Enable. On valid → call `next()`; on invalid → respond `401 Unauthorized` Endpoints NOT mounted under `l402(...)` pass through untouched. So you can mix paid and free routes freely. :::warning l402-express 0.1.x verifies macaroon + preimage only — assert the resource yourself when gating multiple tiers Current versions (`l402-express` 0.1.x, `l402-server` 0.1.x) call the verify endpoint with **only the macaroon and preimage** — they do not pass or compare the request path or amount. Merchant binding and expiry are always enforced by the hosted API, but a token bought for one of your routes will also pass verification on a *different* route gated under the same API key, within the token's validity window (60 minutes by default). If you gate **multiple prices or resources** (e.g., `/api/cheap` at 10 sats and `/api/premium` at 500 sats), add a resource assertion in your handler by comparing `res.locals.l402.resource` against the current request's path: ```js router.get("/forecast", l402({ apiKey: KEY, priceSats: 100 }), (req, res) => { if (res.locals.l402.resource !== req.path) { return res.status(401).json({ error: "Token was purchased for a different resource" }); } res.json({ forecast: "7-day data..." }); }); ``` One Express subtlety: the default bound resource is `req.path` *as the middleware sees it*. In per-route usage (above) that's the same value your handler sees, so the comparison is direct. But when you mount with `app.use("/api/premium", l402(...))`, Express strips the mount prefix inside the middleware, so the token gets bound to the mount-relative path (`/weather`, not `/api/premium/weather`). In that pattern, bind the full path explicitly and compare against the same expression: ```js app.use("/api/premium", l402({ apiKey: KEY, priceSats: 500, resource: (req) => req.baseUrl + req.path, // bind the full path })); app.get("/api/premium/weather", (req, res) => { if (res.locals.l402.resource !== req.baseUrl + req.path) { return res.status(401).json({ error: "Token was purchased for a different resource" }); } res.json({ temp: 72 }); }); ``` An SDK update that passes the request path through to the verify call automatically is in progress; this applies to the 0.1.x releases. A single mount with a single flat price is unaffected in practice — every token you issue is for the same class of access. ::: ## What the caller sees ### Without payment ```bash curl -i https://your-api.example/api/premium/weather ``` ```http HTTP/1.1 402 Payment Required Content-Type: application/json WWW-Authenticate: L402 macaroon="AgELbWFjYXJvb24...", invoice="lnbc1u1p3..." { "error": "Payment Required", "l402": { "macaroon": "AgELbWFjYXJvb24...", "invoice": "lnbc1u1p3...", "amount_sats": 100, "payment_hash": "abc123...", "expires_at": "2026-05-12T01:00:00Z", "resource": "/api/premium/weather" } } ``` ### With payment After paying the Lightning invoice and extracting the preimage: ```bash curl -i https://your-api.example/api/premium/weather \ -H 'Authorization: L402 AgELbWFjYXJvb24...:deadbeef...' ``` ```http HTTP/1.1 200 OK Content-Type: application/json { "temp": 72 } ``` ## Variable per-request pricing Pass a function instead of a static `priceSats` to derive the price from the request: ```js app.use("/api/llm", l402({ apiKey: process.env.LIGHTNING_ENABLE_API_KEY, priceSats: (req) => req.query.model === "premium" ? 500 : 100, })); ``` The function can return a `number` or a `Promise`. Use it for tiered pricing, user-based pricing, dynamic cost-of-goods scenarios, etc. `resource` and `description` accept the same function-or-static shape. ## Configuration reference | Option | Type | Default | Notes | |---|---|---|---| | `apiKey` | `string` | required (one of `apiKey` / `client`) | Merchant API key | | `client` | `L402Server` | — | Pre-constructed SDK client; use to share across mounts | | `priceSats` | `number \| (req) => number \| Promise` | **required** | Price in satoshis, ≥ 1 | | `resource` | `string \| (req) => string \| Promise` | `req.path` | Bound as a macaroon caveat | | `description` | `string \| (req) => string \| undefined` | none | Shown in the payer's Lightning wallet | | `idempotencyKey` | `(req) => string \| undefined` | client IP | Sends `X-Idempotency-Key` for retry-safe challenge issuance | | `baseUrl` | `string` | `https://api.lightningenable.com` | Override producer API URL (testing) | | `onInvalidToken` | `(req, res, failure, next) => void \| Promise` | sends `401` | Custom failure handler — useful for sending a fresh `402` instead | ## Accessing the verified credential in your handler After a successful verification the middleware sets `res.locals.l402` so downstream handlers can see what was paid for: ```js app.get("/api/premium/weather", (_req, res) => { const { resource, amountSats, paymentHash } = res.locals.l402; console.log(`Served ${resource} for ${amountSats} sats (${paymentHash})`); res.json({ temp: 72 }); }); ``` Useful for usage logging, per-endpoint analytics, fraud detection. ## Mounting patterns ### One price for everything ```js app.use(l402({ apiKey: KEY, priceSats: 50 })); // every endpoint below this costs 50 sats ``` ### Different prices for different sub-paths ```js app.use("/api/cheap", l402({ apiKey: KEY, priceSats: 10 })); app.use("/api/premium", l402({ apiKey: KEY, priceSats: 500 })); ``` ### Per-route in a Router ```js import express from "express"; import { l402 } from "l402-express"; const router = express.Router(); router.get("/forecast", l402({ apiKey: KEY, priceSats: 100 }), forecastHandler); router.get("/historical", l402({ apiKey: KEY, priceSats: 50 }), historyHandler); router.get("/free-info", freeInfoHandler); // not gated app.use("/api", router); ``` ## Sharing one SDK client across mounts Each `l402(...)` call constructs its own internal `L402Server` instance. For high-throughput apps you may prefer one shared instance with shared HTTP connection pooling: ```js import { L402Server } from "l402-server"; import { l402 } from "l402-express"; const client = new L402Server({ apiKey: process.env.LIGHTNING_ENABLE_API_KEY }); app.use("/api/cheap", l402({ client, priceSats: 10 })); app.use("/api/premium", l402({ client, priceSats: 500 })); ``` Mutually exclusive with `apiKey` — pass one or the other. ## Custom failure handling By default an invalid `L402` token returns `401 Unauthorized`. Some merchants prefer to issue a fresh `402` so the caller can retry without having to make a separate request first: ```js app.use(l402({ apiKey: KEY, priceSats: 100, onInvalidToken: async (_req, res, failure, _next) => { // Send a fresh 402 challenge instead of 401. // (You'd typically mint a new challenge via the SDK here. Sketch only — // see the runnable app at // https://github.com/refined-element/l402-example-node for a complete // integration.) res.status(402).json({ error: "Token rejected; please pay again", details: failure.error }); }, })); ``` When `onInvalidToken` is supplied, the middleware does NOT send the default 401 — you are responsible for either sending a response or calling `next()`. ## Troubleshooting ### "Bad Gateway" 502 on every request The middleware couldn't reach Lightning Enable. Check: - `LIGHTNING_ENABLE_API_KEY` is set and valid - No outbound firewall blocking `api.lightningenable.com` - Subscription is active ### Every request returns 402 even after payment The `Authorization` header probably isn't reaching your Express app. If you're behind a reverse proxy (nginx, Cloudflare, load balancer), confirm the `Authorization` header is being forwarded. ### `403 Forbidden` from upstream L402 isn't enabled on your subscription plan. Check **Dashboard → Settings → Plan** — Native mode requires Agentic Commerce or Agentic Commerce — Business. ## Source and license Both packages are MIT-licensed open source: - [`l402-express` on GitHub](https://github.com/refined-element/le-server-l402-express-node) - [`l402-server` on GitHub](https://github.com/refined-element/le-server-l402-node) Issues, pull requests, and protocol-level discussion at [lightninglabs/L402](https://github.com/lightninglabs/L402) all welcome. ## Next steps - [Producer API reference](./producer-api-reference) — full HTTP surface if you ever want to call the underlying API directly - [Native Integration — ASP.NET Core](./native-integration-aspnet) — same flow for .NET - [Proxy setup walkthrough](./proxy-setup-walkthrough) — if you decide Proxy mode is a better fit ============================================================================== # Native Integration Source: https://docs.lightningenable.com/products/agentic-commerce/native-integration ============================================================================== # Native L402 Integration Lightning Enable supports two integration shapes. **Native mode** is the one where your API stays exactly where it is — same domain, same auth, same observability — and Lightning Enable handles the payment protocol via a small middleware you drop into your existing app. ## When to choose Native mode over Proxy mode | | **Proxy mode** | **Native mode** | |---|---|---| | Setup time | ~5 minutes in the dashboard | One `install` + one line of middleware | | Code changes to your API | None | Add one middleware registration | | Traffic flows through Lightning Enable | Yes | **No** — your domain, your servers | | You can keep your existing auth | Difficult (Authorization header is consumed by L402) | Yes | | Custom observability / rate limiting | Limited | Full control | | Best for | Public APIs, experiments, no-existing-auth | Commercial APIs, anything with sensitive infrastructure | If your API has its own authentication, custom rate limiting, observability you don't want to lose, or is hosted somewhere you can't change DNS for — **Native mode is the right answer**. ## The two halves of the protocol The L402 protocol has a consumer side and a producer side. Lightning Enable publishes packages for both: | Side | Audience | Packages | |---|---|---| | **Consumer** | Agents / clients that *pay* for paid APIs | [`l402-requests`](https://www.npmjs.com/package/l402-requests) (Node), [`L402Requests`](https://www.nuget.org/packages/L402Requests) (.NET), [`l402-requests`](https://pypi.org/project/l402-requests/) (Python) | | **Producer** | API providers that *charge* for their endpoints | **The packages described on this page** | If you're calling paid APIs from an agent, you want the **consumer** packages. If you're building a paid API that you want agents to pay for, you want the **producer** packages — that's what the rest of this page is about. ## Two layers: SDK and middleware Each language ships two packages: a **server SDK** (raw HTTP client for our producer API) and a **framework middleware** (drop-in for your web framework, built on top of the SDK). | Stack | SDK | Framework middleware | |---|---|---| | Node + TypeScript | [`l402-server`](https://www.npmjs.com/package/l402-server) | [`l402-express`](https://www.npmjs.com/package/l402-express) | | .NET | [`L402Server`](https://www.nuget.org/packages/L402Server) | [`L402Server.AspNetCore`](https://www.nuget.org/packages/L402Server.AspNetCore) | ### What the SDK does The SDK wraps two Lightning Enable hosted endpoints: - `POST /api/l402/challenges` → `createChallenge()` → mints a Lightning invoice + macaroon - `POST /api/l402/challenges/verify` → `verifyToken()` → validates an incoming L402 credential That's it. ~200 lines of typed HTTP-client glue. The SDK knows nothing about HTTP frameworks, routes, or middleware pipelines. You give it inputs, it calls Lightning Enable, you get outputs. Use the SDK directly if you're calling these from a background job, queue worker, serverless function, or anywhere that isn't a typical HTTP middleware. ### What the middleware does The framework middleware sits on top of the SDK and handles all the wiring for the 90% case (an HTTP API that wants to charge per request): 1. Read the `Authorization: L402 :` header 2. If absent → call `createChallenge()`, respond `402 Payment Required` with the invoice 3. If present → call `verifyToken()`. Valid → call next handler; invalid → respond `401` That's all it does — and it does it so you don't have to write the parse-call-respond dance in every paid route. Most merchants will only ever touch the middleware. The SDK is what the middleware imports under the hood. :::warning Current 0.1.x middleware/SDK versions verify macaroon + preimage only The shipped packages — `l402-server` / `l402-express` 0.1.x (Node) and `L402Server` / `L402Server.AspNetCore` 0.1.x (.NET) — call the verify endpoint with **only the macaroon and preimage**. They do not pass `resource` or `amountSats` (the verify request types don't expose those fields yet), and they do not compare the verified token's resource against the incoming request path. The hosted API still always enforces merchant binding and expiry — but within the token validity window (60 minutes by default), a token bought for one of your paths can pass middleware verification on a **different** path of yours. If you gate **multiple price tiers or resources under one merchant API key**, your handler must compare the verified resource itself: - **Express:** assert `res.locals.l402.resource === req.path` — see the worked example (including a mount-path subtlety) on the [Express page](./native-integration-express) - **ASP.NET Core:** assert `((VerificationResult)HttpContext.Items[L402HttpContextKeys.VerificationResult]!).Resource` matches `Request.Path` — worked example on the [ASP.NET Core page](./native-integration-aspnet) An SDK update that passes the request path through to the verify call automatically is in progress; this note applies to the 0.1.x releases. ::: ## Quick start by stack - **[Express (Node.js)](./native-integration-express)** — `npm install l402-express`, then one line of `app.use(l402({ apiKey, priceSats }))` - **[ASP.NET Core (.NET)](./native-integration-aspnet)** — `dotnet add package L402Server.AspNetCore`, then `app.UseL402()` + `[L402(PriceSats = 100)]` attribute - **More stacks coming** — FastAPI (Python) and Go (`net/http`) are next on the roadmap. Direct producer API calls work today in any language; see the [Producer API Reference](./producer-api-reference). ## Architectural decisions baked into the SDKs These are intentional. Worth knowing what you're getting: - **No protocol code on the client side.** Macaroon signing, preimage hashing, payment-hash linking — all server-side at Lightning Enable. The SDK is HTTP-client glue. If the protocol evolves, the hosted endpoint changes; you don't update your code. - **Verification via the hosted endpoint, not local key material.** Every `verifyToken` call goes to `/api/l402/challenges/verify`. One round-trip per paid request (~50ms regional, ~200ms cross-continent). We don't distribute the L402 root key to merchants — that's centralized for security and for replay protection. - **Token reuse within the validity window is a feature.** Macaroon caveats (path, merchant ID, amount, expires) bound the token's validity. The producer API enforces some caveats unconditionally and others on opt-in: - **`merchant_id`** is always enforced server-side — Merchant A can never verify a token bound to Merchant B (cross-tenant IDOR guard). - **`expires`** is always enforced — a presented token past its window returns `valid: false`. - **`path`** is enforced when the integrator passes `resource` on the verify call; otherwise it's read out for the integrator to compare. - **`amount_sats`** is enforced when the integrator passes `amountSats` on the verify call; otherwise read out only. Within the token validity window (60 minutes by default via `DefaultTokenValiditySeconds`), the same `paymentHash` can be re-verified for the same resource — useful for an agent making many quick calls within one paid window. The invoice itself expires sooner (10 minutes default) but the token, once paid, remains valid for the full token-validity window. If your endpoint needs strict single-use semantics, track `paymentHash` locally in your handler. See [Token reuse within the validity window](./producer-api-reference#token-reuse-within-the-validity-window) for the full contract. - **No credentials stored anywhere.** Lightning Enable never asks for your upstream API credentials. The middleware never touches your authentication. Your secrets stay on your servers. ## Prerequisites - An active Lightning Enable subscription — **Agentic Commerce** ($49/month, 30-day free trial via self-serve checkout) or **Agentic Commerce — Business** ([contact us](mailto:support@lightningenable.com) — contact-only, not purchasable through self-serve checkout; any trial terms are arranged directly). - A payment provider account — Strike (recommended) or OpenNode — with your API key saved in **Dashboard → Settings**. - A Lightning Enable merchant API key. Generate it at **Dashboard → Settings → API Keys**. - An existing HTTP API (or a fresh one) on Node + Express or .NET + ASP.NET Core. Other stacks are coming; if you want to use the raw HTTP API today in another language, see the [Producer API Reference](./producer-api-reference). ## What the merchant pays for The middleware is open-source MIT. The SDK is open-source MIT. Lightning Enable's value is the hosted protocol broker — the API endpoint that mints macaroons, signs them with our root key, verifies preimages, enforces caveats (merchant scoping always; path + amount on opt-in), and integrates with your Lightning wallet (Strike / OpenNode / LND / NWC). Your Lightning Enable subscription pays for that broker, plus the dashboard for configuring it, plus the registry for letting agents discover your API. The middleware code itself is free for anyone to read, audit, and modify. ## Testing your integration Three practical ways to exercise a Native integration before real traffic hits it: 1. **Testnet invoices via an OpenNode dev key.** Configure an OpenNode **dev** (testnet) API key under **Dashboard → Settings → Payment Provider**. Challenges minted through your middleware then carry testnet invoices you can pay with testnet sats — no real money moves. Alternatively, keep your production provider and set a **1-sat price** on a staging route, paying the invoices from your own wallet; you're out ~1 sat per test plus routing fees. 2. **Unit-test without any network calls.** Both SDKs let you inject the HTTP layer, so you can stub the producer API's responses: - **Node:** the `L402Server` constructor accepts a custom `fetch` implementation (`new L402Server({ apiKey, fetch: mockFetch })`) — return canned challenge/verification JSON from your mock. - **.NET:** `L402ServerClient` has a constructor overload that accepts an external `HttpClient` (designed for `IHttpClientFactory`) — hand it an `HttpClient` built on a fake `HttpMessageHandler` that returns canned responses. 3. **Smoke-test against the live example apps.** The runnable reference apps at [`l402-example-node`](https://github.com/refined-element/l402-example-node) and [`l402-example-aspnet`](https://github.com/refined-element/l402-example-aspnet) are complete, working integrations — curl them to see exactly what a healthy 402 → pay → 200 flow looks like, and diff their setup against yours when something misbehaves. ## Next steps - [Pick your stack](#quick-start-by-stack) and follow the integration walkthrough - [Producer API Reference](./producer-api-reference) — full HTTP endpoint reference if you want to call our API directly - [Proxy setup walkthrough](./proxy-setup-walkthrough) — if Proxy mode is the right fit after all ============================================================================== # Overview Source: https://docs.lightningenable.com/products/agentic-commerce/overview ============================================================================== # Agentic Commerce :::tip Producer Account Required (free options available) **Creating** L402-protected API endpoints requires a Lightning Enable producer account. Start free with the **Free Producer Sandbox** (3 endpoints, capped monthly challenge volume, 1,000 sats max per challenge — no card), or [activate a 30-day Agentic Commerce trial by paying a ~100-sat L402 challenge](/getting-started/activate-with-lightning) — no card. Full production use is **Agentic Commerce** ($49/month) or **Business** ([contact us](mailto:support@lightningenable.com)). **Using** the MCP server to access L402 APIs is completely free — no account needed. [View pricing](/products/product-overview) ::: L402 enables **pay-per-request API monetization** using the Lightning Network. Users pay a Lightning invoice to access your API - no accounts, no subscriptions, no credit cards required. :::tip Start Testing Immediately **Strike (recommended):** Create a [Strike account](https://dashboard.strike.me) and generate an API key — no KYB required to start. Strike supports preimage extraction for L402. **OpenNode:** Create an [OpenNode dev account](https://app.dev.opennode.com) for testnet testing — no KYB required. Swap to a production key after KYB verification (2-4 business days). [Strike Setup →](/strike-setup/account-setup) | [OpenNode Setup →](/opennode-setup/account-setup) ::: ## Zero Infrastructure Required Unlike other L402 implementations, Lightning Enable is a **fully hosted SaaS**: - **No Lightning node** — your payment provider (Strike or OpenNode) handles all payment routing and liquidity - **No liquidity management** — no channel balancing, no inbound capacity planning - **No custom servers to run** — no Docker, no Kubernetes; we host the L402 protocol layer for you - **No protocol expertise** — configure via dashboard in minutes **What you need:** 1. A Lightning Enable producer account — the free [Producer Sandbox or no-card L402 Fast Lane trial](/getting-started/activate-with-lightning), or an Agentic Commerce ($49/month) / Business ([contact us](mailto:support@lightningenable.com)) subscription 2. A payment provider account — [Strike](https://dashboard.strike.me) (recommended) or [OpenNode](https://app.dev.opennode.com) 3. 5 minutes to configure your first proxy in the [dashboard](/products/agentic-commerce/dashboard-guide) ## What is L402? L402 (formerly LSAT) combines: - **HTTP 402 Payment Required** - The web's native payment status code - **Lightning Network** - Instant Bitcoin micropayments - **Macaroons** - Cryptographic bearer tokens with permissions ### The Problem Traditional API monetization requires: - User accounts and passwords - Credit cards on file - Monthly subscriptions (even for light usage) - Complex billing infrastructure - Fraud prevention systems ### The Solution With L402: - **No accounts** - Pay and access immediately - **No credit cards** - Bitcoin only - **Pay-per-use** - Only pay for what you consume - **Instant access** - Payment confirms in ~1 second - **Global** - Anyone with Bitcoin can access ## How It Works ``` 1. Client requests API 2. Server returns 402 ─────────────────────────────> <───────────────────── GET /api/premium/data HTTP 402 Payment Required Invoice: lnbc100n1p... Macaroon: AgEL... 3. Client pays invoice 4. Client retries with proof ─────────────────────> ─────────────────────────> Via Lightning wallet Authorization: L402 mac:preimage (~1 second) 5. Server verifies, grants access <───────────────────────── HTTP 200 OK { "data": "..." } ``` ## Key Benefits | Traditional API | L402 API | |-----------------|----------| | Account required | No account needed | | Credit card on file | No payment method stored | | Monthly subscription | Pay per request | | Minimum $5–$20 charge | As low as 1 sat (~$0.0004) | | Chargeback risk | Impossible to reverse | | KYC for users | Permissionless access | ## Agent-to-Agent Commerce (Producer API) :::tip New: Agents That Earn With the L402 Producer API, your AI agents can **charge** for their services — not just pay for others'. Your agent creates L402 payment challenges, other agents pay them, and your agent verifies payment before granting access. This is the missing piece for true agentic commerce. **One producer MCP tool, `l402_producer`, action-based:** - `action="create"` — Create a 402 challenge with Lightning invoice + macaroon - `action="verify"` — Verify an L402 token to confirm payment before granting access - Plus `action="configure_receive"`, `"status"`, `"create_proxy"`, `"add_endpoint"`, `"publish"`, and `"list_challenges"` — enough to take an API from unconfigured to live and discoverable without the dashboard. See [Sell With Your Agent](/getting-started/sell-with-your-agent) for the walkthrough. Beyond this, the MCP server also ships the agent-to-agent marketplace (`agent_services`) tool and `create_lightning_enable_account` (self-serve signup over Lightning) — see the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the full tool list. [Read the full L402 Producer API guide](/products/agentic-commerce/l402-producer-api) ::: ## Use Cases ### API Monetization Charge per API call for any service: ```json { "endpoints": [ { "path": "/api/ai/gpt4", "price": 500 }, { "path": "/api/ai/dalle", "price": 1000 }, { "path": "/api/ai/whisper", "price": 100 } ] } ``` ### Premium Content Unlock articles, reports, or media: ```json { "endpoints": [ { "path": "/api/articles/premium/*", "price": 50 }, { "path": "/api/reports/*", "price": 500 } ] } ``` ### Data APIs Pay-per-query data access: ```json { "endpoints": [ { "path": "/api/market-data/*", "price": 10 }, { "path": "/api/analytics/*", "price": 25 } ] } ``` ### Proxy Monetization Monetize third-party APIs you have access to: ```json { "proxy": { "name": "Premium Weather API", "target": "https://api.weather.com", "price": 5 } } ``` ## Quick Example ### Request Without Payment ```bash curl https://api.yourservice.com/api/premium/data ``` **Response: 402 Payment Required** ```json { "error": "Payment Required", "l402": { "macaroon": "AgELbGlnaHRuaW5nLWVuYWJsZQ...", "invoice": "lnbc100n1p3...", "amount_sats": 100, "payment_hash": "abc123..." } } ``` ### Pay the Invoice Use any Lightning wallet (Phoenix, Muun, Zeus, etc.) to pay. Get the preimage. ### Request With Payment Proof ```bash curl https://api.yourservice.com/api/premium/data \ -H "Authorization: L402 AgELbGlnaHRuaW5nLWVuYWJsZQ...:abc123def456..." ``` **Response: 200 OK** ```json { "data": "Premium content here..." } ``` ## Pricing **Agentic Commerce: $49/month** | **Agentic Commerce — Business: [Contact us](mailto:support@lightningenable.com)** Both plans include L402 protocol support. Agentic Commerce is the self-serve plan for individual developers; Agentic Commerce — Business adds white-glove onboarding and direct founder access for teams — [contact us](mailto:support@lightningenable.com) for details. Agentic Commerce includes: - Unlimited L402 endpoints - Strike as settlement provider - Per-endpoint pricing - Live dashboard + per-request payment feed Agentic Commerce — Business includes everything in Agentic Commerce, plus: - White-glove onboarding - Direct founder access (no ticket queues) Not sure where to start? The **Free Producer Sandbox** (3 endpoints, 200 challenges/month, 1,000 sats max per challenge, no card) is the risk-free way to prove L402 works before you subscribe. Your payment provider may charge their own processing fees.* These go to the provider, not to us. *Check your provider's current fee schedule for details. ## Architecture Lightning Enable handles the complexity: ``` +-----------------+ | Your API | | (Protected) | +--------+--------+ | v +--------+--------+ +------------------+ +-----------------+ | L402 Middleware |----->| Lightning Enable |----->| Strike/OpenNode | | (Auth Check) | | (Invoice/Verify) | | (Payment) | +-----------------+ +------------------+ +-----------------+ | | If valid L402 credential v +-----------------+ | Your API Logic | | (Execute) | +-----------------+ ``` :::info Hosted Service Lightning Enable runs as a hosted service at api.lightningenable.com. You configure it via the [dashboard](/products/agentic-commerce/dashboard-guide) or REST API — you don't deploy or manage it. ::: ## Getting Started 1. **Get a producer account** — start free with the [Producer Sandbox or the ~100-sat L402 Fast Lane trial](/getting-started/activate-with-lightning), or subscribe to Agentic Commerce ($49/month) / Business ([contact us](mailto:support@lightningenable.com)) 2. **Set up a payment provider:** - **Strike (recommended):** [Create account](https://dashboard.strike.me) → generate API key → ready immediately - **OpenNode:** [Create dev account](https://app.dev.opennode.com) → no KYB required for testnet 3. **Log in to the [dashboard](https://api.lightningenable.com/dashboard)** — enter your email for a magic link 4. **Add your API key** in Dashboard → Settings → Payment Provider 5. **Create your first proxy** — point it at any API you want to monetize 6. **Test a payment** — verify your integration end-to-end 7. **Go live** — for OpenNode, complete KYB (2-4 days) and swap to a production key ## Next Steps - [Sell With Your Agent](/getting-started/sell-with-your-agent) - **Zero to a paid endpoint**, driven end to end by an MCP agent — no dashboard required - [L402 Producer API](/products/agentic-commerce/l402-producer-api) - **Make your agents earn** with L402 challenges - [Dashboard Guide](/products/agentic-commerce/dashboard-guide) - **Visual walkthrough with screenshots** - [How It Works](/products/agentic-commerce/how-it-works) - Technical deep dive - [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) - **Full MCP documentation** - [API Monetization](/products/agentic-commerce/api-monetization) - Protect your APIs - [Proxy Configuration](/products/agentic-commerce/proxy-configuration) - Monetize any API - [OpenAPI document](/products/agentic-commerce/proxy-setup-walkthrough#openapi-document) - Every published proxy also serves a standard OpenAPI 3.1 document with `x-payment` pricing, for tools that don't speak the L402 manifest format - [AI Agent Integration](/products/agentic-commerce/ai-agent-integration) - Advanced MCP usage - [Wallet Configuration](/products/agentic-commerce/mcp-wallet-setup) - Detailed wallet setup ## Resources - [L402 Protocol Spec (Lightning Labs)](https://github.com/lightninglabs/L402) - [Lightning Network](https://lightning.network) - [Macaroons Paper](https://research.google/pubs/pub41892/) ============================================================================== # Producer API Reference Source: https://docs.lightningenable.com/products/agentic-commerce/producer-api-reference ============================================================================== # L402 Producer API Reference The producer API is what you call to sell: mint a challenge, verify the credential the payer brings back, read what you have minted, and get told when a challenge is paid. Everything an integrator needs is on this page. If you're on Node or .NET, the [SDKs and middleware](./native-integration) wrap these endpoints with idiomatic ergonomics — you can use them instead. This page is for direct integrators on stacks we don't ship a package for yet, or for understanding what the SDK is doing under the hood. | Endpoint | What it does | |---|---| | `POST /api/l402/challenges` | Mint an invoice + macaroon for a resource | | `GET /api/l402/challenges` | List what you have minted, with payment status | | `GET /api/l402/challenges/{paymentHash}` | Look up one challenge | | `POST /api/l402/challenges/verify` | Verify a macaroon + preimage | | `POST /api/l402/challenges/verify-credential` | Verify a modern `Payment` bearer token (single-use) | ## Base URL ``` https://api.lightningenable.com ``` ## Authentication Every request requires your Lightning Enable merchant API key in the `X-API-Key` header. Generate one at **Dashboard → Settings → API Keys**. ```http X-API-Key: ``` The key is tied to your merchant account and to an Agentic Commerce subscription (Agentic Commerce at $49/mo, or Business — [contact us](mailto:support@lightningenable.com)). L402 must be enabled on your plan — Native mode is included with both Agentic Commerce tiers. ## `POST /api/l402/challenges` Mint a Lightning invoice and macaroon for a given resource. Returns the components of a 402 Payment Required challenge that you present to the caller. ### Request ```http POST /api/l402/challenges HTTP/1.1 Host: api.lightningenable.com X-API-Key: Content-Type: application/json Idempotency-Key: { "resource": "/api/premium/weather", "priceSats": 100, "description": "Premium weather forecast" } ``` **Body:** | Field | Type | Required | Notes | |---|---|---|---| | `resource` | string (≤ 848 chars) | yes | The path/resource the challenge is for. Bound as a caveat in the macaroon — the resulting token is only valid for this resource. Longer values are rejected with `400` before any invoice is created. | | `priceSats` | integer (≥ 1) | yes | Price in satoshis. | | `description` | string (≤ 500 chars) | no | Embedded in the Lightning invoice; visible to the payer in their wallet UI. Longer values are rejected with `400`. The string that reaches the invoice is truncated to **200 UTF-8 bytes**, ending in `…`, to stay inside the BOLT11 description field and the payment providers' own limits — so keep anything the payer needs to read at the front. Omit the field, or send a blank one, and you get `L402 access: {resource}`, truncated the same way. | | `idempotencyKey` | string (≤ 200 chars) | no | Same meaning as the `Idempotency-Key` header, for clients that can't set headers. The header wins if you send both. See [Idempotency](#idempotency). | **Headers:** | Header | Required | Notes | |---|---|---| | `X-API-Key` | yes | Merchant API key | | `Content-Type` | yes | `application/json` | | `Idempotency-Key` | no | If supplied, the same challenge is returned for repeat calls with the same key for the life of that invoice. At most 200 characters — a longer key is a `400`, never a truncation. See [Idempotency](#idempotency). | | `X-Idempotency-Key` | no | The spelling this API shipped with. Still accepted and identical in behaviour; `Idempotency-Key` wins if you send both. | ### Response — 200 OK ```json { "invoice": "lnbc1u1p3...", "macaroon": "AgELbWFjYXJvb24=...", "paymentHash": "abc123...", "expiresAt": "2026-05-12T01:00:00Z", "resource": "/api/premium/weather", "priceSats": 100, "mppChallenge": "Payment id=\"k9Q3...\", realm=\"lightning-enable\", method=\"lightning\", intent=\"charge\", request=\"eyJhbW91bnQiOi...\", expires=\"2026-05-12T01:00:00Z\", invoice=\"lnbc1u1p3...\", amount=\"100\", currency=\"sat\"" } ``` | Field | Type | Notes | |---|---|---| | `invoice` | string | BOLT11 Lightning invoice the caller must pay | | `macaroon` | string | **URL-safe base64** (base64url) macaroon containing the payment hash and caveats. Uses `-`/`_` instead of `+`/`/` and may omit padding — decode with a base64url-aware function (`base64.urlsafe_b64decode` in Python, `Buffer.from(s, 'base64url')` in Node, `WebEncoders.Base64UrlDecode` in .NET) rather than standard base64. | | `paymentHash` | string | Hex payment hash linking the macaroon to the invoice | | `expiresAt` | string (ISO 8601) | When the Lightning invoice expires | | `resource` | string | Echoes the request's `resource` | | `priceSats` | integer | Echoes the request's `priceSats` | | `mppChallenge` | string \| null | Ready-to-emit `WWW-Authenticate: Payment ...` value for the same invoice. It carries both the modern draft-00 parameters (`id`, `realm`, `method`, `intent`, `request`, `expires`) and the legacy `invoice` / `amount` / `currency` parameters, so any `Payment`-scheme client can use it. Serve it alongside the `L402` header. `null` only when MPP is switched off at the service level (it is on for the hosted API). See [Payment (MPP) credentials](/api-reference/l402#payment-mpp-credentials). | ### Error responses Errors on this endpoint are [RFC 9457 problem documents](#error-format) — `application/problem+json`, with a stable `type` URI to branch on and the pre-RFC `error` / `message` members still present. | Status | `type` suffix | Meaning | |---|---|---| | `400` | — | Data-annotation failure (missing `resource`, `resource` over 848 characters, `priceSats < 1`). Returns ASP.NET Core's model-state ProblemDetails (`{ "type", "title", "errors": { ... } }`), which has no Lightning Enable `type` suffix. | | `400` | `invalid_idempotency_key` | The key was blank or longer than 200 characters. | | `400` | `invalid_resource` | `resource` exceeds the 848-character limit. Carries `max_length`. | | `400` | `payment_provider_not_configured` | No Strike or OpenNode key on your account, so no invoice could be created. Carries `docs`. This is the most common refusal on a brand-new account. | | `400` | `challenge_creation_failed` | Another business-logic refusal from the challenge service. | | `401` | `authentication_required` / `merchant_not_found` | Missing, malformed, or revoked `X-API-Key`. | | `402` | `plan_price_limit` / `plan_volume_limit` / `plan_endpoint_limit` | A plan cap was reached. Each carries `current_plan` and `upgrade_url`. | | `403` | — | L402 is not enabled on your plan. Body includes `current_plan` and `action_required: "upgrade_plan"`. | | `409` | `idempotency_key_reuse` | The key was already used for a different `resource` or `priceSats`. Carries `bound_resource` / `bound_price_sats` and `requested_resource` / `requested_price_sats`. | | `409` | `endpoint_retired` | The resource was retired to free a plan slot and can no longer mint. | | `429` | — | Rate-limited. | | `503` | `challenge_persist_failed` | The challenge could not be durably recorded, so none was issued — **nothing was invoiced**. Safe to retry. | --- ## `GET /api/l402/challenges` List the challenges you have minted, newest first, with their payment status. Scoped to the account behind your API key — there is no tenant parameter to pass and no way to see anyone else's. ### Request ```http GET /api/l402/challenges?status=paid&since=2026-09-01T00:00:00Z&limit=50&offset=0 HTTP/1.1 Host: api.lightningenable.com X-API-Key: ``` | Query parameter | Type | Default | Notes | |---|---|---|---| | `status` | `paid` \| `unpaid` \| `expired` | none | Omit to list everything. Any other value is a `400`. | | `since` | ISO 8601 timestamp | none | Lower bound on `createdAt`. Send an offset (`Z` or `+02:00`); a bare timestamp is read as UTC. | | `limit` | integer | `50` | Clamped to 1..200 rather than rejected. | | `offset` | integer | `0` | Clamped to at least 0. | ### Response — 200 OK The total matching your filter, ignoring paging, is also in the `X-Total-Count` header. ```json { "challenges": [ { "paymentHash": "abc123...", "resource": "/api/premium/weather", "amountSats": 100, "status": "paid", "createdAt": "2026-09-05T18:00:00Z", "paidAt": "2026-09-05T18:00:41Z", "expiresAt": "2026-09-05T19:00:00Z", "idempotencyKey": "req-abc-123" } ], "total": 1, "limit": 50, "offset": 0, "status": "paid", "since": null } ``` | Field | Notes | |---|---| | `paymentHash` | Hex payment hash of the challenge's invoice. The correlation handle — safe to log and store. | | `status` | `paid` once a credential from this challenge has verified; `unpaid` while the token window is open; `expired` after it closes without proof of payment. `paid` is permanent — a challenge paid inside its window never reverts to `expired`. | | `paidAt` | When payment was first **proven** — the first successful verification of a credential from this challenge. `null` while unproven, which includes an invoice that was paid but whose credential you have never presented back. | | `expiresAt` | End of the token window; matches the macaroon's `expires` caveat. `null` on rows minted before this was recorded. | | `idempotencyKey` | The key the challenge was minted under, if you sent one. | :::note What this is not `paidAt` is proof-of-payment, not a settlement record. Lightning Enable does not hold funds — the sats settled with your payment provider the moment the invoice was paid, which may be earlier than this timestamp. Your provider's dashboard remains the record of what you were paid. ::: Never returned: the macaroon and the preimage. A preimage is bearer money; the payment hash is what you correlate on. ### Error responses | Status | `type` suffix | Meaning | |---|---|---| | `400` | `invalid_status_filter` | `status` was not one of the three values. Carries `allowed_status`. | | `401` | `authentication_required` | Missing or invalid `X-API-Key`. | --- ## `GET /api/l402/challenges/{paymentHash}` Look up a single challenge you minted. ```http GET /api/l402/challenges/abc123... HTTP/1.1 X-API-Key: ``` Returns the same object as one element of the list above. ### Error responses | Status | `type` suffix | Meaning | |---|---|---| | `400` | `invalid_payment_hash` | Not 64 hexadecimal characters. | | `401` | `authentication_required` | Missing or invalid `X-API-Key`. | | `404` | `challenge_not_found` | You have no challenge with that payment hash. A hash belonging to another account returns this same `404` — never a `403`, which would confirm the hash exists. | --- ## `POST /api/l402/challenges/verify` Verify an L402 credential — a macaroon + preimage pair presented in an `Authorization: L402` header from a caller who paid your challenge. ### Request ```http POST /api/l402/challenges/verify HTTP/1.1 Host: api.lightningenable.com X-API-Key: Content-Type: application/json { "macaroon": "AgELbWFjYXJvb24=...", "preimage": "deadbeef..." } ``` **Body:** | Field | Type | Required | Notes | |---|---|---|---| | `macaroon` | string | required for L402 | The URL-safe base64 (base64url) macaroon from the caller's `Authorization` header — pass through unchanged, no re-encoding. Omit only if doing MPP-style preimage-only verification (and MPP is enabled on your account). | | `preimage` | string (hex, 64 chars) | yes | The payment preimage proving the invoice was paid. | | `resource` | string \| null | recommended | The path the caller is gating. **If you provide it, the producer API enforces the macaroon's `path` caveat against this value** — a mismatch returns `valid: false`. If you omit it, the path caveat is read out but not enforced (the integrator is responsible for the comparison). | | `amountSats` | integer \| null | recommended | The price tier the gated endpoint requires. **If you provide it, the producer API enforces the macaroon's `amount_sats` caveat against this value** — prevents replaying a cheap token against an expensive endpoint matched by a wildcard rule. If you omit it, the amount caveat is read out but not enforced. | :::tip Defense in depth Two enforcement guarantees are **always** applied server-side regardless of which optional fields you pass: - **Authenticated merchant_id is always compared to the macaroon's `merchant_id` caveat.** Calling the verify endpoint as merchant B with a token bound to merchant A returns `valid: false`. There is no opt-out — this is the cross-tenant IDOR guard. - **Macaroon signature, preimage hash, and `expires` caveat are always verified.** The optional `resource` and `amountSats` fields opt you into additional path/amount caveat enforcement. Pass them whenever you have the values handy; the only reason to skip is a generic verifier that doesn't know the gated path up front. ::: ### Response — 200 OK The producer API returns **200 OK for both valid and invalid tokens** — read the `valid` field rather than relying on the status code. **Valid token:** ```json { "valid": true, "resource": "/api/premium/weather", "merchantId": 42, "amountSats": 100, "paymentHash": "abc123..." } ``` **Invalid token:** ```json { "valid": false, "error": "Invalid preimage" } ``` | Field | Type | Notes | |---|---|---| | `valid` | bool | The gate. Inspect this. | | `error` | string \| null | Failure reason; only populated when `valid: false`. Examples: `"Invalid preimage"`, `"Token bound to a different resource"`, `"Macaroon signature invalid"`. | | `resource` | string \| null | The path/resource the token is bound to (from the macaroon's caveat). Assert this matches the resource the caller is actually requesting. | | `merchantId` | integer \| null | The merchant ID the macaroon was issued under. | | `amountSats` | integer \| null | The amount the token was issued for. | | `paymentHash` | string \| null | The payment hash from the macaroon's identifier. | ### Error responses Also [RFC 9457 problem documents](#error-format). | Status | `type` suffix | Meaning | |---|---|---| | `400` | `invalid_verification_request` | A field was present but unusable — a blank `macaroon`, a blank `resource`, `amountSats` below 1. | | `400` | `mpp_not_supported` | Preimage-only verification requested but MPP is not enabled. | | `401` | `authentication_required` / `merchant_not_found` | Missing or invalid `X-API-Key`. | | `403` | — | L402 not enabled on your plan. | Note: a *valid macaroon* with an *invalid preimage* still returns `200 OK` with `valid: false`. Non-2xx is reserved for auth / plan / transport problems. **Side effect:** the first successful verification of a credential marks the underlying challenge paid and fires [`l402.challenge.paid`](#payment-webhooks). Later verifications of the same credential do not re-fire it. --- ## `POST /api/l402/challenges/verify-credential` Verify a **modern `Payment` bearer credential**: the `Authorization: Payment ` token defined by `draft-httpauth-payment-00` + `draft-lightning-charge-00`. Use `/verify` for classic L402 tokens and legacy `Payment method="lightning", preimage="..."` credentials; use this endpoint for the bearer form. Format details: [Payment (MPP) credentials](/api-reference/l402#payment-mpp-credentials). **This endpoint consumes the credential.** A modern credential is single-use by design: the first successful verification marks it consumed atomically, and a second call with the same token returns `valid: false`. Verify once, then serve the resource. ### Request ```http POST /api/l402/challenges/verify-credential X-API-Key: YOUR_MERCHANT_API_KEY Content-Type: application/json ``` | Field | Type | Required | Notes | |---|---|---|---| | `credential` | string | Yes | The token as received. The leading `Payment ` scheme word is optional. | | `resource` | string | No | When set, the challenge must have been minted for this resource. | | `amountSats` | integer | No | When set, the challenge must have been minted for exactly this price. | ### Response — 200 OK ```json { "valid": true, "consumed": true, "resource": "/api/premium/weather", "merchantId": 42, "amountSats": 100, "paymentHash": "abc123...", "receipt": "eyJjaGFsbGVuZ2VJZCI6Ims5UTMuLi4iLC..." } ``` | Field | Notes | |---|---| | `valid` | `true` when the preimage matches, the challenge binding is intact, the credential is unexpired, and it had not been consumed before. | | `consumed` | `true` when this call consumed the credential. | | `receipt` | base64url(JCS) receipt: `{"challengeId","method":"lightning","reference":"","status":"success","timestamp"}`. Return it to the payer as the `Payment-Receipt` response header. The reference is the payment hash, never the preimage. | | `error` | Present when `valid` is `false`. A static description, never the token or preimage. | ### Error responses | Status | `type` suffix | Meaning | |---|---|---| | `400` | `mpp_not_supported` | Modern credentials are switched off on this server. | | `401` | `authentication_required` / `merchant_not_found` | Missing or invalid `X-API-Key`. | | `403` | `l402_not_enabled` | L402 not enabled on your plan. | A malformed or already-consumed token returns `200 OK` with `valid: false`, the same convention as `/verify`. **Side effect:** the first successful verification marks the underlying challenge paid and fires [`l402.challenge.paid`](#payment-webhooks). --- ## Error format Every error the producer API returns itself is `application/problem+json` ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457.html)): ```json { "type": "https://lightningenable.com/problems/idempotency_key_reuse", "title": "Idempotency key reuse", "status": 409, "detail": "This idempotency key was already used for a different resource or price. …", "error": "idempotency_key_reuse", "message": "This idempotency key was already used for a different resource or price. …", "bound_resource": "/api/premium/weather", "bound_price_sats": 100 } ``` **Branch on `type`.** It is a stable identifier that never changes meaning, and it is safe to switch on in code. `title` and `detail` are prose written for a human reading a log, and may be reworded. `error` and `message` are the pre-RFC members, kept so integrations written against the older shape keep working — `error` always equals the `type` suffix, and `message` always equals `detail`, except on a handful of paths that shipped a different `error` string before RFC 9457 and keep it verbatim. Endpoint-specific members (`current_plan`, `max_length`, `docs`, `bound_price_sats`, …) sit alongside them. `type` URIs are identifiers, not URLs to fetch — nothing is served at them. One exception: request-shape failures caught by model binding (a missing `resource`, `priceSats` below 1) come from ASP.NET Core's own validation and use its `{ "type", "title", "errors": { … } }` ProblemDetails, with no Lightning Enable `type` suffix. Read `errors` for the per-field detail there. --- ## Token reuse within the validity window L402 tokens remain valid for **repeated** verifications until the macaroon's `expires` caveat passes. The default is **60 minutes** from issuance (`L402Options.DefaultTokenValiditySeconds = 3600`). During that window the producer API returns `valid: true` for any verification of a valid macaroon + preimage pair, including replays of the same pair. This is intentional, not a bug. Two separate durations to understand: - **Token validity (60 min default)** — controlled by `DefaultTokenValiditySeconds`, embedded as an `expires` caveat in the macaroon. This is the window during which a *paid* token can be re-presented and verified successfully. - **Invoice expiry (10 min default)** — controlled by `InvoiceExpirySeconds`. This is the window during which the *Lightning invoice itself* can be paid. After this, the invoice is dead and the macaroon is moot regardless of its expiry caveat. Caveat enforcement on `POST /api/l402/challenges/verify`: - **`merchant_id` caveat** — **always enforced** against the authenticated merchant id (derived from your `X-API-Key`). Merchant A cannot verify a macaroon that was bound to merchant B. No opt-out. - **`path` caveat** — **enforced when you pass `resource` in the verify request body**. Without `resource`, the path caveat is reported in the response (`resource` field) but not compared — the integrator is responsible for the check. - **`amount` caveat** — **enforced when you pass `amountSats` in the verify request body**. Without `amountSats`, the amount is reported in the response but not compared. - **`expires` caveat** — always enforced. A token presented after its validity window returns `valid: false`. Pre-2026-05 (before the verify endpoint switched to context-aware verification), `path` and `amount` caveats were always read out but never compared; integrators had to do the comparison themselves. They still can, but passing `resource` / `amountSats` on the request now opts into stricter server-side checks. New integrations should pass them; existing integrations that already do client-side comparison can omit them without breaking anything. Caveats do NOT prevent same-resource reuse within the validity window. That's by design: a paid agent making many quick calls within one paid window is a legitimate use case, and the burden of caching preimages on the consumer side is real (the open-source `l402-requests` clients don't do it by default). **If you specifically need single-use semantics** for a particular endpoint (e.g., a one-shot model that returns expensive state), track consumed preimages locally in your handler. A trivial in-memory set keyed on `paymentHash` works for single-process apps; Redis or your existing database works for distributed deployments. The verification result includes `paymentHash` precisely so you can do this without re-parsing the macaroon. --- ## Idempotency A retry must never produce a second payable invoice. Send an `Idempotency-Key` on `POST /api/l402/challenges` and you get the same challenge back — same invoice, same macaroon, same payment hash — for the life of that invoice: ```http POST /api/l402/challenges Idempotency-Key: req-abc-123 { "resource": "/api/premium/weather", "priceSats": 100 } ``` A replayed response carries `X-Idempotency-Replayed: true`. The body is byte-identical to the first one, so a client that ignores the header sees exactly what it saw before. The key is recorded on the challenge itself, not in a cache, so the replay survives a deploy, a restart, and a load balancer sending your retry to a different instance. It is scoped to your account: two merchants can use the same key string without colliding. **Rules:** - **Same key, same `resource` and `priceSats`** → the original challenge, replayed. `description` is not part of the match, so changing only the description still replays. - **Same key, different `resource` or `priceSats`, while the original invoice is still live** → `409` with `type: .../idempotency_key_reuse`. One key means one live charge; guessing which of the two you meant risks handing you an invoice for the wrong amount. Use a fresh key for a new charge. - **Key whose invoice has expired** → the key is released and a fresh challenge is minted under it, **whatever you ask for**. An expired binding holds no payable invoice, so there is nothing left to conflict with: reusing a spent key at a different price is a normal mint, not a `409`. Invoices are payable for 10 minutes by default (`L402Options.InvoiceExpirySeconds`). - **Two requests at once with one key** → exactly one mints. The other gets that same challenge if it asked for the same charge, or the same `409` it would have got from a sequential retry if it asked for a different one. Either way you are never charged twice for a race. - **Blank, or longer than 200 characters** → `400`. The key is never truncated: truncating would collapse two distinct keys onto one challenge, which is the exact double-charge the key exists to prevent. This means a key derived from the work you are doing — `order-9182-challenge` — keeps working across the whole life of that order: it replays inside the invoice window and mints fresh after it, and it is never poisoned by a price change. Clients that can't set headers can send `idempotencyKey` in the body instead. `X-Idempotency-Key` — the spelling this API shipped with — still works and behaves identically. **If you send no key**, nothing changes from before: the server deduplicates by `(merchantId, clientIP, resource, priceSats)` for the invoice window, using a per-process cache. That is usually right for middleware on a single server, and wrong behind a load balancer or across a restart — pass an explicit key in those cases. `POST /api/l402/challenges/verify` is a read-only check on the macaroon + preimage. Repeated verification of the same pair during the token validity window returns the same `valid: true` result every time — see [Token reuse within the validity window](#token-reuse-within-the-validity-window) above. (The first one also marks the challenge paid; see [Payment webhooks](#payment-webhooks).) --- ## Payment webhooks When a challenge you minted is first proven paid, Lightning Enable POSTs `l402.challenge.paid` to your account's callback URL. Set one under **Dashboard → Settings → Webhooks**; with no callback configured, nothing is sent and nothing is queued. ```json { "event": "l402.challenge.paid", "paymentHash": "abc123...", "resource": "/api/premium/weather", "amountSats": 100, "paidAt": "2026-09-05T18:00:41Z", "idempotencyKey": "req-abc-123" } ``` Signed with the same `X-LightningEnable-Signature: t={timestamp},v1={hmac_sha256}` scheme and delivered by the same retrying forwarder as every other Lightning Enable webhook — see [Webhooks](../../api-reference/webhooks) for signature verification and retry behaviour. **"Paid" means proven, not settled.** The event fires the first time a credential minted from that challenge verifies successfully — through `/verify`, `/verify-credential`, or an L402-gated proxy request. The payer can only hold that credential by having settled the invoice with your payment provider, so it is proof of payment; but the sats moved when the invoice was paid, which may be moments earlier. Lightning Enable does not hold funds, and your provider remains the record of what you were paid. It fires **once per challenge**. L402 tokens stay valid for repeated use until their `expires` caveat, so the same credential is verified many times per paid invoice; only the first transition notifies. An invoice that was paid but whose credential is never presented back to Lightning Enable produces no event — there is nothing to prove it. Poll `GET /api/l402/challenges?status=unpaid` if you need to reconcile those against your provider. Each delivery attempt signs the same body with a fresh timestamp, so dedupe on `paymentHash`, never on the signature value. --- ## End-to-end example flows ### Mint + present a challenge ```bash # 1. Caller requests your endpoint without paying $ curl -i https://your-api.example/api/premium/weather HTTP/1.1 402 Payment Required WWW-Authenticate: L402 macaroon="AgEL...", invoice="lnbc1u..." Content-Type: application/json { "error": "Payment Required", "l402": { ... } } ``` In your handler before responding with that 402, you called: ```bash $ curl -X POST https://api.lightningenable.com/api/l402/challenges \ -H 'X-API-Key: $LIGHTNING_ENABLE_API_KEY' \ -H 'Content-Type: application/json' \ -d '{"resource":"/api/premium/weather","priceSats":100}' { "invoice": "lnbc1u1p3...", "macaroon": "AgEL...", "paymentHash": "abc123", "expiresAt": "2026-05-12T01:00:00Z", "resource": "/api/premium/weather", "priceSats": 100 } ``` ### Verify a returning request ```bash # Caller pays the invoice, gets the preimage, retries with credential: $ curl -i https://your-api.example/api/premium/weather \ -H 'Authorization: L402 AgEL...:deadbeef...' ``` In your handler: ```bash $ curl -X POST https://api.lightningenable.com/api/l402/challenges/verify \ -H 'X-API-Key: $LIGHTNING_ENABLE_API_KEY' \ -H 'Content-Type: application/json' \ -d '{"macaroon":"AgEL...","preimage":"deadbeef..."}' { "valid": true, "resource": "/api/premium/weather", "merchantId": 42, "amountSats": 100, "paymentHash": "abc123" } ``` Once you see `valid: true`, serve the response. Note that this example did **not** include `resource` in the verify body, so the path caveat was reported back but not compared server-side — asserting that the returned `resource` matches the path the caller is requesting is your responsibility here. To get server-side enforcement instead, include the path in the verify request: ```bash -d '{"macaroon":"AgEL...","preimage":"deadbeef...","resource":"/api/premium/weather"}' ``` With `resource` supplied, a token bound to a different path returns `valid: false` — see [caveat enforcement rules](#token-reuse-within-the-validity-window) above. --- ## Language-specific quick references These are the minimum to call the producer API from each language. For richer ergonomics use the SDKs/middlewares. ### Node.js (without the SDK) ```js const response = await fetch("https://api.lightningenable.com/api/l402/challenges", { method: "POST", headers: { "X-API-Key": process.env.LIGHTNING_ENABLE_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ resource: "/api/premium/weather", priceSats: 100, }), }); const challenge = await response.json(); ``` Prefer the [`l402-server`](https://www.npmjs.com/package/l402-server) SDK or [`l402-express`](https://www.npmjs.com/package/l402-express) middleware. ### .NET (without the SDK) ```csharp using var http = new HttpClient(); http.DefaultRequestHeaders.Add("X-API-Key", apiKey); var body = JsonContent.Create(new { resource = "/api/premium", priceSats = 100 }); var response = await http.PostAsync("https://api.lightningenable.com/api/l402/challenges", body); var challenge = await response.Content.ReadFromJsonAsync(); ``` Prefer [`L402Server`](https://www.nuget.org/packages/L402Server) or [`L402Server.AspNetCore`](https://www.nuget.org/packages/L402Server.AspNetCore). ### Python (no SDK yet — Phase 2 of the Native L402 roadmap) ```python import os import requests response = requests.post( "https://api.lightningenable.com/api/l402/challenges", headers={ "X-API-Key": os.environ["LIGHTNING_ENABLE_API_KEY"], "Content-Type": "application/json", }, json={"resource": "/api/premium/weather", "priceSats": 100}, ) challenge = response.json() ``` A Python SDK (`lightningenable-l402-server`) and FastAPI middleware (`lightningenable-fastapi-l402`) are in development. ### Go (no SDK yet — Phase 2 of the Native L402 roadmap) ```go body, _ := json.Marshal(map[string]any{ "resource": "/api/premium/weather", "priceSats": 100, }) req, _ := http.NewRequest("POST", "https://api.lightningenable.com/api/l402/challenges", bytes.NewReader(body)) req.Header.Set("X-API-Key", os.Getenv("LIGHTNING_ENABLE_API_KEY")) req.Header.Set("Content-Type", "application/json") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() var challenge map[string]any json.NewDecoder(resp.Body).Decode(&challenge) ``` A Go SDK (`github.com/refined-element/l402-server-go`) with `net/http` middleware is on the Phase 2 roadmap. --- ## Versioning The producer API is stable. New optional fields may be added to request/response bodies without a version bump; breaking changes ship behind a versioned path (`/api/v2/...`) with overlap. Subscribe to release notes at https://docs.lightningenable.com/release-notes. ## Rate limits Lightning Enable rate-limits per merchant API key. Limits are generous for typical traffic; if you're hitting them you'll see `429 Too Many Requests`. Contact support if you need higher limits. ## Support Open issues at the relevant SDK/middleware repo, or contact us at **support@lightningenable.com**. ## See also - [Sell With Your Agent](/getting-started/sell-with-your-agent) — an MCP agent driving every endpoint on this page end to end, from an empty account to a live, verified paid endpoint ============================================================================== # Proxy Configuration Source: https://docs.lightningenable.com/products/agentic-commerce/proxy-configuration ============================================================================== # Proxy Configuration The L402 Proxy allows you to monetize any API - yours or third-party - by creating a payment-gated reverse proxy. :::info No Infrastructure Required The L402 proxy is a fully hosted service. You configure it; Lightning Enable runs it. No Lightning node, no servers, no Docker. ::: ## Overview The proxy sits between clients and target APIs: ``` Client → L402 Proxy → Target API │ └─ Requires Lightning payment ``` Use cases: - **Monetize your own APIs** — in Proxy mode (no modification) or via [native middleware](./native-integration) (one-line drop-in) - **Resell third-party APIs** with a markup - **Create premium access** to public APIs - **Rate-limit expensive APIs** via micropayments ## Creating a Proxy ### Via Dashboard (Recommended) Navigate to [api.lightningenable.com/dashboard/proxies/create](https://api.lightningenable.com/dashboard/proxies/create) and follow the 4-step wizard: 1. **Basic Info** — name and description 2. **Target URL** — the API you want to monetize 3. **Pricing** — satoshis per request + token validity 4. **Review** — confirm and create ![Create Proxy Wizard](/img/dashboard/proxy-create-step1.png) For a full visual walkthrough, see the [Dashboard Guide](/products/agentic-commerce/dashboard-guide). ### Via API ```bash curl -X POST https://api.lightningenable.com/api/proxy \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{ "name": "Premium Weather API", "targetBaseUrl": "https://api.weather.com/v1", "defaultPriceSats": 10, "description": "Weather data with Lightning payments" }' ``` Response (`201 Created`): ```json { "id": 12, "proxyId": "premium-weather-api-a1b2", "name": "Premium Weather API", "description": "Weather data with Lightning payments", "targetBaseUrl": "https://api.weather.com/v1", "defaultPriceSats": 10, "isActive": true, "createdAt": "2026-07-03T12:00:00Z", "requestCount": 0, "totalSatsEarned": 0, "endpointPricingCount": 0, "proxyUrl": "/l402/proxy/premium-weather-api-a1b2", "endpointPricings": [] } ``` The `proxyId` slug is generated from the proxy name. If the slug collides with an existing proxy, a 4-character hex suffix is appended (as in the example above); otherwise the clean slug is used as-is. ### Proxy Endpoint Your proxy is now available at: ``` https://api.lightningenable.com/l402/proxy/{proxyId}/{path} ``` For example: ``` https://api.lightningenable.com/l402/proxy/premium-weather-api-a1b2/forecast?city=nyc ``` ## Configuration Options ### Configuration Reference Create (`POST /api/proxy`) accepts: | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Display name for the proxy (1–100 chars). Also used to generate the `proxyId` slug. | | `targetBaseUrl` | string | Yes | Base URL of the target API. Must be `http`/`https` with a public domain name (see [SSRF Protection](#ssrf-protection)). | | `defaultPriceSats` | int | No | Default price per request in satoshis (1–1,000,000). Defaults to 10 if omitted. | | `description` | string | No | Proxy description (up to 500 chars). | Update (`PUT /api/proxy/{proxyId}`) accepts the same fields — all optional — plus: | Field | Type | Required | Description | |-------|------|----------|-------------| | `isActive` | bool | No | Enable/disable the proxy. Disabled proxies return `404` to clients. | There are no per-proxy method filters, path allow/block lists, or timeout settings. The proxy forwards **all** HTTP methods (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) and **all** paths under `/l402/proxy/{proxyId}/*` to the target API, with a fixed 30-second upstream timeout. If you need to restrict which endpoints are reachable, enforce that on your upstream API itself, or point `targetBaseUrl` at a narrower base path (e.g., `https://api.example.com/v1/public`). :::warning No path filtering Because there is no `allowedPaths`/`blockedPaths` setting, any path on your target API under the configured base URL is reachable through the proxy (after payment). Never point a proxy at an API surface that includes admin or internal endpoints you don't want exposed. ::: One limit does apply: the full L402 resource string `/l402/proxy/{proxyId}/{path}` can be at most **848 characters**. A longer request gets `414 resource_too_long` before any invoice is created — shorten the downstream path (or the proxy ID). ### Token Validity Each proxy has a configurable token validity period — how long a paid L402 token remains usable after payment. Set it in the dashboard wizard (**Step 3: Pricing**) when creating the proxy; if not set, tokens fall back to the global default of 1 hour. :::note Per-proxy token validity was fixed in the July 2026 update; earlier tokens always used the 1-hour default regardless of the value entered in the wizard. ::: ## Endpoint-Specific Pricing :::tip Dashboard Alternative You can manage endpoint-specific pricing visually in the [dashboard](/products/agentic-commerce/dashboard-guide#pricing-tab) — no API calls needed. ::: Set different prices for different endpoints: ```bash curl -X POST https://api.lightningenable.com/api/proxy/{proxyId}/pricing \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{ "pathPattern": "/forecast/7day", "priceSats": 50, "description": "7-day forecast (premium)" }' ``` ### Multiple Price Tiers ```bash # Add pricing for different endpoints curl -X POST .../pricing -d '{"pathPattern": "/current/*", "priceSats": 5}' curl -X POST .../pricing -d '{"pathPattern": "/forecast/1day", "priceSats": 10}' curl -X POST .../pricing -d '{"pathPattern": "/forecast/7day", "priceSats": 50}' curl -X POST .../pricing -d '{"pathPattern": "/historical/*", "priceSats": 100}' ``` ### Price Matching Priority Pricing rules are evaluated in ascending `priority` order (lower number = checked first; the default is `0`). The **first active rule whose `pathPattern` matches** the request path wins — there is no specificity ordering beyond that. If no rule matches, the proxy's `defaultPriceSats` applies. If you have overlapping patterns (e.g., `/forecast/7day` and `/forecast/*`), give the more specific rule a lower `priority` value so it is checked first: ```bash curl -X POST .../pricing -d '{"pathPattern": "/forecast/7day", "priceSats": 50, "priority": 0}' curl -X POST .../pricing -d '{"pathPattern": "/forecast/*", "priceSats": 10, "priority": 10}' ``` ## Using the Proxy ### Request Flow 1. Client requests proxy endpoint (no auth): ```bash curl https://api.lightningenable.com/l402/proxy/premium-weather-api-a1b2/forecast?city=nyc ``` 2. Proxy returns 402 with invoice: ```json { "error": "Payment Required", "message": "Pay the Lightning invoice to access this API", "proxy": { "id": "premium-weather-api-a1b2", "name": "Premium Weather API", "description": "Weather data with Lightning payments" }, "l402": { "macaroon": "AgEL...", "invoice": "lnbc100n1p...", "amount_sats": 10, "payment_hash": "abc123...", "expires_at": "2026-07-03T12:10:00.0000000Z" }, "instructions": { "step1": "Pay the Lightning invoice using any Lightning wallet", "step2": "Copy the preimage (proof of payment) from your wallet", "step3": "Include in request: Authorization: L402 : (or Authorization: Payment method=\"lightning\", preimage=\"\")" } } ``` 3. Client pays invoice, gets preimage 4. Client retries with L402 credential: ```bash curl https://api.lightningenable.com/l402/proxy/premium-weather-api-a1b2/forecast?city=nyc \ -H "Authorization: L402 AgEL...:abc123..." ``` 5. Proxy forwards request to target, returns response: ```json { "city": "New York", "forecast": [...] } ``` ## Proxy Management ### List Your Proxies ```bash curl https://api.lightningenable.com/api/proxy \ -H "X-API-Key: your-merchant-api-key" ``` ### Get Proxy Details ```bash curl https://api.lightningenable.com/api/proxy/{proxyId} \ -H "X-API-Key: your-merchant-api-key" ``` ### Update Proxy ```bash curl -X PUT https://api.lightningenable.com/api/proxy/{proxyId} \ -H "X-API-Key: your-merchant-api-key" \ -d '{"defaultPriceSats": 20}' ``` ### Delete Proxy ```bash curl -X DELETE https://api.lightningenable.com/api/proxy/{proxyId} \ -H "X-API-Key: your-merchant-api-key" ``` ## Target API Authentication Lightning Enable **does not store or inject upstream API credentials**. The proxy forwards client headers transparently and strips the incoming `Authorization` header (it consumes that one for the L402 payment check). There is no `authHeader`/`authValue` configuration. If the API you are proxying requires its own authentication (an API key, bearer token, signed requests), you have two options: 1. **Merchant-side auth shim** — put a thin endpoint you control between the proxy and the credentialed API: the proxy targets your endpoint, and your endpoint attaches the credential when calling the upstream. See [Handling APIs that require authentication](/products/agentic-commerce/proxy-setup-walkthrough#handling-apis-that-require-authentication) in the setup walkthrough for the full pattern. 2. **Native integration** — skip the proxy and add L402 payment gating directly inside your own API with the [native middleware](./native-integration). Your code keeps full control of upstream credentials; they never leave your infrastructure. ## Analytics :::tip Dashboard Alternative The same lifetime totals are visible on the proxy detail page's [Overview tab](/products/agentic-commerce/dashboard-guide#overview-tab) in the dashboard. Per-day charts and per-path breakdowns are on the roadmap but not available yet. ::: ### Get Proxy Analytics ```bash curl https://api.lightningenable.com/api/proxy/{proxyId}/analytics \ -H "X-API-Key: your-merchant-api-key" ``` Response — lifetime totals for the proxy (there is no time-period filtering or per-path breakdown): ```json { "proxyId": "premium-weather-api-a1b2", "name": "Premium Weather API", "totalRequests": 5234, "totalSatsEarned": 52340, "averageRevenuePerRequest": 10.00, "createdAt": "2026-05-01T09:00:00Z", "lastUpdated": "2026-07-03T11:58:21Z" } ``` ## Testing Proxy ### Test Target Reachability ```bash curl -X POST https://api.lightningenable.com/api/proxy/{proxyId}/test \ -H "X-API-Key: your-merchant-api-key" ``` Response: ```json { "success": true, "targetUrl": "https://api.weather.com/v1", "statusCode": 200, "statusDescription": "OK", "responseTimeMs": 234, "message": "Target API is reachable" } ``` On failure, `success` is `false` and `message` explains why (e.g., `"Request timed out after 10 seconds"` or `"Connection failed: ..."`). The test request uses a 10-second timeout and runs the same SSRF checks as live proxy traffic. ### Test with Specific Path Pass the path as a **query parameter** (there is no JSON request body): ```bash curl -X POST "https://api.lightningenable.com/api/proxy/{proxyId}/test?path=/forecast" \ -H "X-API-Key: your-merchant-api-key" ``` ## Example Configurations ### Public Weather API Proxy An API that needs no upstream credentials — the simplest case: ```json { "name": "Weather Data", "description": "Public weather data with Lightning payments", "targetBaseUrl": "https://api.open-meteo.com/v1", "defaultPriceSats": 5 } ``` ### Your Own API with Tiered Pricing Point the proxy at your API and layer endpoint-specific prices on top of the default: ```json { "name": "Market Data", "description": "Market data API, pay-per-request", "targetBaseUrl": "https://api.your-domain.com/v2", "defaultPriceSats": 10 } ``` Endpoint pricing (added via `POST /api/proxy/{proxyId}/pricing`): ```json [ { "pathPattern": "/quotes/realtime/*", "priceSats": 50, "priority": 0 }, { "pathPattern": "/quotes/*", "priceSats": 10, "priority": 10 } ] ``` ### Credentialed Upstream (via Auth Shim) For an upstream that requires its own API key (e.g., an AI model provider), target a thin endpoint **you** host that attaches the credential — the proxy itself never holds it: ```json { "name": "AI Completions", "description": "Pay-per-request AI completions", "targetBaseUrl": "https://shim.your-domain.com/ai", "defaultPriceSats": 500 } ``` See [Target API Authentication](#target-api-authentication) above for why this pattern is required and what the shim looks like. ## Security The L402 proxy includes several built-in protections to prevent abuse and ensure safe operation. These protections apply automatically to all proxies. ### Request & Response Size Limits The proxy enforces size limits on both inbound requests and upstream responses to prevent memory exhaustion and abuse. | Limit | Default | Config Key | HTTP Status on Violation | |-------|---------|------------|--------------------------| | Request body | 1 MB (1,048,576 bytes) | `L402:MaxProxyRequestBodyBytes` | **413 Payload Too Large** | | Response body | 10 MB (10,485,760 bytes) | `L402:MaxProxyResponseBodyBytes` | **502 Bad Gateway** | **Request body limit** -- When a client sends a POST, PUT, or PATCH request through the proxy, the body is checked against the configured maximum. If the `Content-Length` header is present and exceeds the limit, the request is rejected immediately. If the header is absent, the body is read incrementally and rejected as soon as it exceeds the limit. **Response body limit** -- When the upstream target API returns a response, its size is checked the same way: first via `Content-Length` header for an early rejection, then by streaming and monitoring the total bytes read. If the response exceeds the limit, the proxy returns a 502 to the client instead of the oversized response. These limits are set at the Lightning Enable service level (`L402:MaxProxyRequestBodyBytes` / `L402:MaxProxyResponseBodyBytes`) — they are not per-proxy merchant settings. If your use case needs larger payloads, contact support. **Example error response (413):** ```json { "error": "Payload Too Large", "message": "Request body size (2,500,000 bytes) exceeds the maximum allowed size (1,048,576 bytes)", "proxy_id": "premium-weather-api-a1b2" } ``` **Example error response (502 for oversized upstream response):** ```json { "error": "Bad Gateway", "message": "Response from target API (15,000,000 bytes) exceeds the maximum allowed size (10,485,760 bytes)", "proxy_id": "premium-weather-api-a1b2" } ``` ### SSRF Protection Server-Side Request Forgery (SSRF) protections prevent proxies from being used to access internal infrastructure. Validation happens at **two stages**: when configuring a proxy and at runtime when forwarding each request. #### Configuration-Time Validation When you create or update a proxy, the `targetBaseUrl` is validated against these rules: | Rule | Details | |------|---------| | **Scheme** | Only `http` and `https` are allowed. Other schemes (e.g., `file://`, `ftp://`) are rejected. | | **Hostname** | Raw IP addresses are not allowed; a domain name is required. | | **Localhost rejection** | `localhost`, `localhost.localdomain`, `ip6-localhost`, and `ip6-loopback` are blocked. | | **Internal domain suffixes** | Hostnames ending in `.local`, `.internal`, `.localhost`, or `.svc.cluster.local` are blocked. | | **Port restrictions** | Only standard HTTP ports (80, 443) are allowed. Non-standard ports are rejected. | | **DNS resolution check** | If the hostname resolves at configuration time, all resolved IPs are checked for private ranges. If DNS resolution fails (e.g., the domain is not yet set up), the proxy is allowed but will be checked again at runtime. | #### Runtime DNS Rebinding Prevention Even if a domain passes configuration-time validation, it is checked again **on every proxied request**. Before connecting to the target API, the proxy resolves the hostname and verifies that none of the resolved IP addresses fall into private or reserved ranges. This prevents DNS rebinding attacks where an attacker changes a domain's DNS records to point to internal IPs after the proxy is configured. **Blocked IP ranges:** | Range | Description | |-------|-------------| | `127.0.0.0/8` | IPv4 loopback | | `10.0.0.0/8` | RFC 1918 private | | `172.16.0.0/12` | RFC 1918 private | | `192.168.0.0/16` | RFC 1918 private | | `169.254.0.0/16` | IPv4 link-local | | `0.0.0.0/8` | Current network | | `::1` | IPv6 loopback | | `fc00::/7` | IPv6 unique local | | `fe80::/10` | IPv6 link-local | | IPv4-mapped IPv6 | e.g., `::ffff:127.0.0.1` (mapped to IPv4 and checked) | If a blocked IP is detected at runtime, the proxy returns a **502 Bad Gateway** response: ```json { "error": "Bad Gateway", "message": "The target API address is not allowed", "proxy_id": "premium-weather-api-a1b2" } ``` :::tip These protections are fully automatic. You do not need to configure anything -- they are always active for all proxies. ::: ## Error Handling ### Proxy Errors | Status | Body `error` | Cause | Solution | |--------|--------------|-------|----------| | 404 | `Proxy not found` | Unknown **or disabled** proxy ID | Check the proxy ID; re-enable the proxy if you disabled it | | 404 | `Proxy unavailable` | The merchant account behind the proxy is inactive | Reactivate the merchant account | | 404 | `Ambiguous proxy ID` | An unsuffixed alias matches more than one proxy | Use the full proxy ID including its hex suffix | | 502 | `Bad Gateway` | Target API unreachable / connection failed | Check the target URL and target API health | | 504 | `Gateway Timeout` | Target API didn't respond within 30 seconds | Speed up the upstream endpoint (the timeout is fixed) | | 413 | `Payload Too Large` | Request body exceeds size limit | Reduce request body or contact provider about limits | | 414 | `resource_too_long` | The full `/l402/proxy/{proxyId}/{path}` resource exceeds 848 characters | Shorten the downstream path or the proxy ID; no invoice is created for the refused request | | 502 | `Bad Gateway` (size) | Upstream response exceeds size limit | Contact provider about response size limits | | 502 | `Bad Gateway` (SSRF) | Target resolves to private IP | Use a public domain name for your target API | Error bodies include a `message` field with details and (for gateway errors) a `proxy_id` field. ### Payment / Credential Errors The proxy **never returns 401 or 403**. Any missing, malformed, expired, or otherwise invalid L402 credential results in a **fresh `402 Payment Required` challenge** — a new invoice and macaroon — with the failure reason in the `X-L402-Error` response header and in the body's `message` field. | Status | Meaning | What the client should do | |--------|---------|---------------------------| | 402 (no `X-L402-Error`) | First request — payment required | Pay the invoice, retry with `Authorization: L402 :` | | 402 + `X-L402-Error` | The presented credential failed verification (bad format, wrong preimage, expired token, wrong path/merchant/price) | Read the header for the reason; pay the **new** invoice from this response and retry | Clients that branch on 401/403 will never hit those branches — treat every 402 as a (re-)challenge. ## Best Practices ### Security - Keep upstream API credentials on your own infrastructure (auth shim or native integration) — the proxy does not store them - Scope `targetBaseUrl` to the narrowest base path that serves your paying clients; every path under it is reachable - Never expose an API surface containing admin or internal endpoints through a proxy - Monitor usage for abuse - Use HTTPS target URLs whenever possible ### Pricing - Research target API costs - Add reasonable markup (20-50%) - Consider volume discounts - Price based on value, not cost ### Reliability - Keep upstream responses under the fixed 30-second proxy timeout - Handle target API errors gracefully - Monitor target API availability - Have fallback targets if possible ## Next Steps - [Dashboard Guide](/products/agentic-commerce/dashboard-guide) - Visual proxy management walkthrough - [API Reference](/api-reference/l402) - Complete L402 API docs - [How It Works](/products/agentic-commerce/how-it-works) - Technical details - [FAQ](/faq) - Common questions ============================================================================== # Setting Up Your Proxy (Full Walkthrough) Source: https://docs.lightningenable.com/products/agentic-commerce/proxy-setup-walkthrough ============================================================================== # Setting Up Your Proxy This guide walks you through every step required to take an existing API and make it discoverable and payable by AI agents using the L402 protocol. You do not need to change any code in your API. **Who this is for:** API providers who want AI agents to find their API, understand what it does, and pay per request in Bitcoin over Lightning. If you are an agent developer looking to consume an existing L402 API, see [MCP Quickstart](/products/agentic-commerce/mcp-quickstart) instead. **What you will have at the end:** A live proxy URL that enforces per-request Lightning payments, a public manifest that AI agents can read to discover your endpoints and pricing, and an optional listing in the public L402 registry. --- ## Table of Contents 1. [Prerequisites](#prerequisites) 2. [Step 1 — Create a proxy](#step-1--create-a-proxy) 3. [Step 2 — Set a default price](#step-2--set-a-default-price) 4. [Step 3 — Configure the manifest](#step-3--configure-the-manifest) 5. [Step 4 — Add your endpoints](#step-4--add-your-endpoints) 6. [Step 5 — Review pricing per endpoint](#step-5--review-pricing-per-endpoint) 7. [Step 6 — Publish and share](#step-6--publish-and-share) 8. [Troubleshooting](#troubleshooting) --- ## Prerequisites - An active Lightning Enable subscription (**Agentic Commerce** at $49/mo, 30-day free trial via self-serve checkout, or **Agentic Commerce — Business** — [contact us](mailto:support@lightningenable.com) — contact-only, not purchasable through self-serve checkout; any trial terms are arranged directly). - A payment provider account — Strike (recommended) or OpenNode — with your API key saved in **Dashboard → Settings**. - An API you own or have permission to proxy. The creation form rejects raw IP-address hosts, internal hostnames (`.local`, `.internal`, `.svc.cluster.local`), and non-standard ports (only 80 and 443 are accepted) — so for most production APIs on standard HTTPS this isn't a constraint you'll think about. The runtime forwarding path additionally blocks targets that resolve to private/internal IP addresses, regardless of port. :::tip Your API needs no changes The proxy wraps your API from the outside. Your API never knows that payments are happening. It receives normal HTTP requests and returns normal HTTP responses. ::: --- ## Step 1 — Create a proxy Open the dashboard at [api.lightningenable.com/dashboard](https://api.lightningenable.com/dashboard) and click **Create Proxy** to launch the guided wizard. ### Name Pick a name your customers and agents will recognize. This appears in the proxy list and in the public manifest. **Good example:** `OpenWeather Current Conditions` **Avoid:** `proxy1`, `test`, `my api` ### Proxy ID Lightning Enable auto-generates a Proxy ID from your name. When you create through the dashboard wizard, the name is lower-cased, spaces become hyphens, and an 8-character hex suffix is appended to keep it unique. For `OpenWeather Current Conditions` you would get something like `openweather-current-conditions-a1b2c3d4`. The Proxy ID becomes part of the URL where your proxy lives: ``` https://api.lightningenable.com/l402/proxy/openweather-current-conditions-a1b2c3d4/ ``` AI agents and clients send their requests to this URL. The proxy forwards them to your actual API. You cannot change the Proxy ID after creation. ### Target URL This is the base URL of the real API you are proxying. Every request that arrives at your proxy URL is forwarded to this address. | Your proxy URL | Gets forwarded to | |---|---| | `/l402/proxy/openweather-current-conditions-a1b2c3d4/current?city=miami` | `https://api.openweathermap.org/data/2.5/current?city=miami` | The dashboard wizard accepts any well-formed `https://` or `http://` URL. Two stricter sets of rules apply on top of that: - **At runtime**, on every forwarded request: targets resolving to private, internal, loopback, or link-local IP addresses are blocked. This is a security check that runs regardless of how the proxy was created. - **When creating via the REST API** (`POST /api/proxy`): in addition to the runtime block above, the API also rejects raw IP-address hosts, non-standard ports, and known internal hostnames (`.local`, `.internal`, `.svc.cluster.local`, etc.). For most production APIs (public HTTPS endpoints on standard ports) you will satisfy both layers without thinking about them. ### Handling APIs that require authentication Lightning Enable **does not** store or inject upstream API credentials. The proxy forwards client headers transparently and strips the incoming `Authorization` header (LE consumes that one for the L402 payment check). If the API you are proxying requires its own auth — an API key, bearer token, signed request, mTLS — that authentication has to happen between your infrastructure and your upstream, not through us. **Recommended pattern: a thin auth-injecting front in your own infrastructure.** Stand up a small reverse proxy or edge worker on a subdomain you control (for example, `monetized-api.your-domain.com`) that: 1. Accepts unauthenticated incoming requests from Lightning Enable 2. Adds your upstream API key / bearer / signature 3. Forwards to your real API Then set your Lightning Enable proxy's **Target URL** to that subdomain instead of your raw API. Your credentials stay in your environment, you can rotate them without touching Lightning Enable, and LE never sees them. **Example — nginx:** ```nginx location / { proxy_pass https://api.your-upstream.com; proxy_set_header Authorization "Bearer YOUR_UPSTREAM_KEY"; proxy_set_header Host api.your-upstream.com; } ``` Adapt the same pattern to whatever edge-proxy / API-gateway / serverless function your stack already uses — the only requirement is that LE-bound traffic gets an `Authorization` header attached before it reaches your real upstream. If your API has no auth (it's already public and you want to add Lightning monetization in front of it), skip this step — just set Target URL to your API directly. :::tip Deeper integration available For commercial APIs where running a separate auth-injecting proxy isn't a fit, [**Native L402 integration**](./native-integration) is now available. Your API validates L402 tokens directly using Lightning Enable's hosted producer endpoints, and Lightning Enable acts as a payment broker that never sees your traffic. Drop-in middleware packages for [Express (Node)](./native-integration-express) and [ASP.NET Core (.NET)](./native-integration-aspnet) ship today; FastAPI (Python) and Go (`net/http`) are in development. ::: --- ## Step 2 — Set a default price After creating the proxy you land on the proxy detail page. Open the **Pricing** tab. The **Default Fallback Price** is the number of satoshis charged for any request that does not match a per-endpoint rule. Think of it as the "everything else" price. If you charge the same amount for every endpoint, set this once and you are done. If you want different prices for different endpoints, set a reasonable base price here and then configure per-endpoint overrides in Step 5. **How to choose a starting price:** The wizard shows a live USD preview next to whatever number you enter (at current rates 100 sats is roughly 6 cents USD, though Bitcoin price moves continuously). Use this table as a rough starting point: | Use case | Suggested starting price | |---|---| | Public data (weather, sports scores) | 10–50 sats | | Enrichment APIs (address validation, geocoding) | 50–200 sats | | AI inference (summarization, classification) | 200–1000 sats | | Proprietary data feeds | Set based on your commercial price | Enter the number in the **Price per Request (sats)** field and click **Save**. --- ## Step 3 — Configure the manifest The manifest is a structured JSON file that lives at a public URL on your proxy. AI agents read it to learn what your API does, what it costs, and how to pay. Without a manifest, your proxy is invisible to agents — they have no way to discover it or know what it costs. With a manifest, your API can appear in the public L402 registry, agents can discover your endpoints automatically, and they can pay per call and use your API autonomously. ### Enable Manifest Turn on the **Enable Manifest** switch. This publishes the manifest file at: ``` https://api.lightningenable.com/l402/proxy/{proxyId}/.well-known/l402-manifest.json ``` Any agent that knows your proxy URL can fetch this file. The proxy also adds a `Link` header to every 402 response pointing to this file, so agents that encounter your proxy for the first time can auto-discover the manifest without knowing the URL in advance. ### Service Description One or two plain-language sentences describing what your API does. This text appears in: - Registry search results (when agents or developers search the public registry by keyword) - The top of the manifest JSON (agents read this to decide if your API is relevant to their current task) Write it for an AI agent, not a marketing audience. Concrete is better than superlative. **Good example:** `Returns current weather conditions and 7-day forecasts for any US city or zip code. Data refreshes every 15 minutes from NWS and NOAA sources.` **Avoid:** `The best weather API — fast, reliable, and affordable!` ### Contact Email A public email address for integration questions. Visible in the manifest. Human developers who want to use your API may reach out here. This field is optional. ### Documentation URL A link to your full API documentation — the human-readable docs you already have. AI agents may follow this URL to get endpoint-level detail beyond what the manifest captures. Optional but recommended if you have docs. ### Terms of Service URL A link to your terms of service or usage policy. Some agents check for a ToS URL before paying and require acceptance before they proceed. Optional. ### Save Settings Click **Save Settings** after filling in any of these fields. The manifest file updates immediately. --- ## Step 4 — Add your endpoints The endpoints table tells agents exactly which paths exist, what each one does, and what it costs. Without endpoints listed, the manifest advertises your default price but gives agents no detail about individual operations. You have two options for populating this table. ### Option A — Scan API (recommended if your API has an OpenAPI spec) If your API serves an OpenAPI (Swagger) specification, the scanner can read it and import all your endpoints automatically. **What Scan API does:** 1. Fetches your API's OpenAPI spec (either from a URL you provide, or by probing standard locations on your target URL). 2. Parses every endpoint: path, HTTP method, summary, description, request schema, response schema. 3. Imports them into the manifest endpoint table with your default price applied to each one. **Standard locations the scanner probes automatically:** When you leave "Spec URL (optional)" blank, the scanner tries these paths on your target URL: - `/openapi.json` - `/swagger/v1/swagger.json` - `/swagger.json` - `/api-docs` - `/v3/api-docs` - `/.well-known/openapi.json` If your spec is at one of these locations, click **Scan API** with the field empty. If the scan succeeds, you will see a confirmation message listing how many endpoints were imported. **If auto-scan fails:** The most common reason is that your spec is served at a non-standard URL. In that case, paste the full URL to your spec in the **Spec URL** field and click **Scan API** again. ``` Example: https://api.example.com/v2/docs/swagger.json ``` If the scan still fails, your API may not publish an OpenAPI spec. Use Option B instead. **What happens after a successful scan:** Discovered endpoints are added to the table with: - Your default fallback price set on each one - L402 payment enabled - The endpoint visible in the public manifest You can adjust any of these settings per-endpoint after import. **Scans are idempotent** — running Scan API a second time only imports endpoints not already in the table. Existing entries are not overwritten. ### Option B — Add Manual Click **Add Manual** to open a form where you enter a single endpoint by hand. Use this when: - Your API does not have an OpenAPI spec - You only want to expose a subset of endpoints - You want to add endpoints with custom descriptions that the spec does not include Fields to fill in: - **Path** — the URL path, starting with `/` (e.g., `/v1/forecast`) - **HTTP Method** — GET, POST, PUT, PATCH, or DELETE - **Summary** — one sentence describing what this endpoint does (shown in the manifest) - **Description** — optional longer explanation (agents may read this for context) - **Base Price (sats)** — sats per request for this specific endpoint The dialog also exposes optional fields you can leave at their defaults: - **Pricing Model** — Per-request (default), Free, or other models for unusual pricing - **Base Price (USD cents)** — set this if you want to price in USD; the manifest converts to sats live at the current rate - **Visible in Manifest** — toggle off to hide an internal endpoint - **L402 Required** — toggle off if this endpoint should be free / not gated - **Rate Limits** — per-minute and per-day caps that agents are expected to respect --- ## Step 5 — Review pricing per endpoint After populating the endpoint table, review the **Price** column on each row. Each row shows the price that the public manifest advertises to agents for that endpoint. This is the amount the agent expects to pay when it reads your manifest. ### Default vs per-endpoint prices There are two layers of pricing: **Default Fallback Price (top of the Pricing tab):** Charged when a request arrives at your proxy but does not match any per-endpoint rule. This is the safety net for paths you have not configured individually. **Per-endpoint prices (in the table):** The price listed in the manifest for a specific endpoint. When an agent pays and sends a request to that path, this is the amount the agent was told to expect. Both values must agree with what the proxy actually charges at runtime. See the Sync section below for how to fix disagreements. ### Editing per-endpoint pricing Click the **edit** icon on any row to change the price, description, summary, or visibility for that endpoint. | Field | Effect | |---|---| | Price (sats) | What the manifest advertises for this endpoint | | Visible | Whether this endpoint appears in the public manifest | | L402 | Whether payment is required for this endpoint | ### The Sync chip When a sats-priced endpoint's manifest price differs from what the proxy would actually charge at runtime, a yellow chip appears on that row: ``` manifest 100 / runtime 50 — Sync ``` This means the manifest is advertising 100 sats but calls to this path will actually cost 50 sats. An agent that reads the manifest and pays the advertised price may be over- or under-charged. This is confusing and can break agents that verify charges. **What causes this:** The most common cause is that a wildcard pricing rule (like `/v1/*` at 50 sats) applies to this path at runtime, but the endpoint was later configured in the manifest with a different price. **What Sync does:** Clicking the chip and confirming writes an exact-path pricing rule that matches only this endpoint's path, at the manifest price, with the highest priority. The wildcard rule remains — it still covers paths not in the table — but this path is now explicitly set to match the manifest. After syncing, the chip disappears from that row. **When the chip does NOT appear (even if the numbers differ):** - **Free endpoints** (`Pricing Model = Free`) — they have no sats price to mirror, so Sync would have nothing meaningful to write - **USD-priced endpoints** (`Base Price (USD cents)` set) — the manifest converts USD to sats live at request time using the current BTC rate, so the runtime sats number is *expected* to drift. Sync is intentionally skipped here - **Endpoints with `Base Price (sats) = 0`** — same reason as Free; nothing positive to mirror - **Config-driven proxies** — runtime pricing lives in App Service config, not the database, so the dashboard can't compute a runtime number to compare against For these cases, if you want the runtime to match the manifest you'll need to address it through the appropriate channel: switch the endpoint to a sats-priced Per Request model and re-save, or update the App Service config directly for a config-driven proxy. --- ## Step 6 — Publish and share ### List in Public Registry Turn on the **List in Public Registry** switch to include your proxy in the public L402 API registry at: ``` https://api.lightningenable.com/api/manifests/registry ``` The registry is where AI agents and developers search for available L402 APIs by keyword or category. Once listed, your API appears in results matching your name, description, and service description. Requirements: - **Enable Manifest** must be on - **Service Description** is strongly recommended — it is the text that appears in search results and determines whether agents consider your API relevant ### The three URLs in Preview & Share The **Preview & Share** section at the bottom of the Pricing tab shows three URLs. **JSON URL** — the machine-readable manifest: ``` /l402/proxy/{proxyId}/.well-known/l402-manifest.json ``` This is what AI agents fetch. Share it with developers who want to write code that uses your API. Open it in a browser to verify everything looks right before announcing your API. **Markdown URL** — a human-readable preview of the same information at `/api/proxy/{proxyId}/manifest.md`. This endpoint is **authenticated** — it requires your merchant API key and is intended for previewing or exporting the manifest as Markdown, not for public sharing. To share the manifest externally, give people the JSON URL above (which is public) or copy the rendered Markdown into your own README / docs. **Registry URL:** ``` https://api.lightningenable.com/api/manifests/registry ``` This is the registry itself, not a URL specific to your proxy. Share it when pointing someone to the broader ecosystem of L402 APIs — for example, in a blog post or announcement. Agents use this URL to search for available APIs by keyword. ### OpenAPI document Every proxy with **Enable Manifest** turned on also serves a standard OpenAPI 3.1 document at: ``` /l402/proxy/{proxyId}/openapi.json ``` (the equivalent `.well-known/openapi.json` path under the same proxy prefix serves the identical document). This is public and unauthenticated — no merchant API key required, same visibility rule as the JSON manifest: it 404s until **Enable Manifest** is on. Use it when the agent or tool you're pointing at your API already knows how to parse OpenAPI and doesn't need to learn the L402 manifest's custom schema. The document has one path item per visible, active manifest endpoint, and every operation carries: - A vendor `x-payment` extension with the price in sats (and USD, for USD-priced endpoints), the payment protocols currently advertised (`L402` and, when enabled, `MPP-draft00`), the token validity window, and the auth header format. - A `402` response object pointing at `components.responses.PaymentRequired`, which documents the `WWW-Authenticate` header carrying the L402 challenge. The document's `servers[0].url` is your proxy's live base URL (`/l402/proxy/{proxyId}`), and its root-level `x-payment` block carries your proxy's default price. Prices in the OpenAPI document and the JSON manifest are computed by the exact same pricing logic — they can never disagree. The JSON manifest's `service` block also links to this document via an `openapi_url` field, and the registry listing for your proxy includes an `openApiUrl` field plus an `x-payment` summary, so an agent that starts at either surface can find the OpenAPI document without a separate lookup. --- ## Troubleshooting ### "Could not find an OpenAPI/Swagger specification at any well-known location" This message appears when Scan API probes all standard locations and finds nothing. It does not mean your API is broken — it means your API does not expose an OpenAPI spec at a standard path, or the spec is protected behind authentication. **Try this:** 1. Check your API docs for a link to your Swagger or OpenAPI spec. 2. Paste the full URL to your spec in the **Spec URL** field and click **Scan API** again. 3. If your spec requires authentication, your API provider will need to either expose a public copy or you will need to add endpoints manually using **Add Manual**. Common non-standard spec paths: - `https://your-api.com/docs/openapi.yaml` - `https://your-api.com/api/swagger` - `https://your-api.com/specification.json` ### "Legacy wildcard pricing rules detected" This warning appears when the proxy has any active pricing rule that uses a wildcard pattern (a `PathPattern` containing `*`, like `/v1/*`). The warning is informational — it surfaces the precedence behavior so you understand what Sync does. The warning by itself does NOT mean anything is wrong. It only means: *if* a manifest endpoint's price differs from what the wildcard would charge for that path, the Sync chip will write a more specific exact-path rule that wins over the wildcard. **If you see yellow Sync chips on rows:** click Sync on each one to write an exact-path rule for that endpoint at its manifest price. The wildcard remains and still applies to paths the table doesn't cover. After every sats-priced row has either matched the wildcard or been explicitly synced, the chips disappear (and the warning becomes informational only). **If you see the warning but no chips:** your manifest endpoints either already match the wildcard, are Free/USD-priced (where the chip is intentionally suppressed), or there are no entries to compare against. No action needed. **If you intended the wildcard price to apply to everything:** update the per-endpoint prices in the table to match the wildcard price, then sync each sats-priced row. ### The manifest shows no endpoints If you enabled the manifest but the endpoint table is empty, the JSON manifest will have an empty `endpoints` array. Agents can still discover your proxy and pay for requests using the default price, but they will not know what individual paths exist. Populate the endpoint table using Scan API or Add Manual. Endpoints with **Visible** turned on appear in the manifest; endpoints with it turned off do not. ### An endpoint I added does not appear in the manifest Check that: - The **Visible** toggle on that row is on (green) - **Enable Manifest** is turned on at the top of the Pricing tab - You clicked **Save Settings** after enabling the manifest ### My proxy is active but agents get a 404 Verify that the Target URL in the Overview tab is correct and reachable. A 404 from the proxy itself means the proxy could not find your API at that URL. A 404 from your API means the path the agent requested does not exist on your API. --- ## Next Steps - [API Monetization](/products/agentic-commerce/api-monetization) — pricing strategies and markup guidance - [Proxy Configuration Reference](/products/agentic-commerce/proxy-configuration) — complete technical reference including security limits and error codes - [How L402 Works](/products/agentic-commerce/how-it-works) — the full payment protocol flow - [AI Agent Integration](/products/agentic-commerce/ai-agent-integration) — how agents consume your proxy ============================================================================== # Product Overview Source: https://docs.lightningenable.com/products/product-overview ============================================================================== # Lightning Enable Products Lightning Enable offers a **Free Producer Sandbox** to start with no card, a self-serve **Agentic Commerce** plan, and **Agentic Commerce — Business** for teams that want white-glove onboarding. All plans use the **BYOA (Bring Your Own API Key)** model — you bring your own **Strike** or **OpenNode** API key, and funds flow directly to your account. ## Plans | Feature | Free Producer Sandbox | Agentic Commerce | Agentic Commerce — Business | |---------|------------------------|-----------|------------------------| | **Price** | Free — no card | $49/month | [Contact us](mailto:support@lightningenable.com) | | **Free Trial** | N/A — Free is the floor | 30 days, self-serve checkout | Arranged on contact | | **Best For** | Hobbyists, demos, side projects | Individual developers | Platforms and teams monetizing APIs, e-commerce, and agent commerce | | **REST API** | ✅ | ✅ | ✅ | | **checkout.js Widget** | ✅ | ✅ | ✅ | | **Webhook Integration** | 1 endpoint | ✅ | ✅ | | **Multi-Currency** | ❌ | ✅ | ✅ | | **L402 Protocol** | ✅ (capped) | ✅ | ✅ | | **Pay-per-Request** | ✅ (capped) | ✅ | ✅ | | **MCP Server** | ✅ | ✅ | ✅ | | **L402 Producer API** | ✅ | ✅ | ✅ | | **White-glove Onboarding** | ❌ | ❌ | ✅ | Free is capped rather than unlimited: 3 L402 endpoints, 200 challenges per month, 1,000 sats maximum per challenge, and 1 proxy configuration. ## Platform Integrations (Included with Any Paid Plan) Platform integrations extend your Agentic Commerce plan with native e-commerce platform support at no additional cost. Not included on the Free Producer Sandbox. | Integration | Description | |-------------|-------------| | **[Shopify Commerce](/products/shopify-commerce/overview)** | L402 agentic commerce for Shopify stores — AI agents browse, pay, and order | ## Architecture All Lightning Enable products share the same architecture: ``` ┌─────────────┐ ┌───>│ Strike │───> Lightning Network Your Platform ──> Lightning Enable ┤ │ (provider) │ (settlement) (middleware) │ └─────────────┘ │ ┌─────────────┐ └───>│ OpenNode │───> Lightning Network │ (provider) │ (settlement) └─────────────┘ ``` **Key points:** - Lightning Enable is API middleware — we never touch your funds - Your chosen provider (Strike or OpenNode) facilitates custody and settlement - You bring your own API key (BYOA model) — choose Strike or OpenNode per merchant - Flat subscription pricing — no per-transaction fees --- :::tip 30-Day Free Trial Agentic Commerce includes a **30-day free trial** with full API access through self-serve checkout. A card is required upfront but you will not be charged until the trial ends. One trial per email. Agentic Commerce — Business is contact-only ([email support@lightningenable.com](mailto:support@lightningenable.com)) — it isn't purchasable through self-serve checkout, and any trial terms are arranged directly. Prefer no card at all? Start with the [Free Producer Sandbox](/getting-started/activate-with-lightning) or the no-card [L402 Fast Lane](/getting-started/activate-with-lightning). See [Free Trial](/products/subscription-management#free-trial) for details. ::: ## Free Producer Sandbox **Free — no card required** | [Sign up](https://api.lightningenable.com/dashboard/signup) Capped L402 access for hobbyists, demos, and side projects. Upgrade to Agentic Commerce when you have real traffic. ### Ideal For - **Hobbyists and demos** - Prove L402 works before you commit to anything - **Side projects** - Test payment flows without a card - **Evaluators** - Kick the tires before recommending Lightning Enable to a team ### What You Get - 3 L402 endpoints - 200 challenges / month - 1,000 sats max per challenge - 1 proxy config - Manifest generation - Community support [Sign Up →](https://api.lightningenable.com/dashboard/signup) --- ## Agentic Commerce **$49/month** | [Documentation](/products/standalone/overview) Full L402 access for individual developers with settlement via Strike API. The fastest way to start building with Lightning payments. ### Ideal For - **Individual developers** - Build and test L402 integrations - **Side projects** - Monetize APIs with Lightning payments - **AI builders** - Connect agents to paid APIs via MCP ### What You Get - Unlimited L402 endpoints - Strike as settlement provider - Per-endpoint pricing - Live dashboard + per-request payment feed [Get Started →](/products/standalone/overview) --- ## Agentic Commerce — Business **[Contact us](mailto:support@lightningenable.com)** | [Documentation](/products/agentic-commerce/overview) Pay-per-request API monetization using the L402 protocol, with white-glove onboarding. Ideal for AI services, premium APIs, and machine-to-machine payments. ### Ideal For - **API providers** - Charge per inference, per token, per request - **Premium data APIs** - Weather, financial, research data - **Content APIs** - News, media, entertainment - **Developer tools** - CI/CD, testing, monitoring services - **MCP server operators** - Monetize AI agent capabilities ### What You Get - Everything in Agentic Commerce - White-glove onboarding - Direct founder access (no ticket queues) - **L402 Producer API** — agents that earn, not just spend ### How L402 Works ``` 1. Client requests protected endpoint 2. Server returns HTTP 402 with Lightning invoice 3. Client pays invoice (1-1000 sats) 4. Client includes proof-of-payment in header 5. Server grants access ``` ### Quick Example ```bash # Request without payment -> 402 Payment Required curl https://api.yourservice.com/premium-data # Response: 402 with invoice + macaroon # Pay invoice with Lightning wallet, get preimage # Request with L402 credential -> 200 OK curl https://api.yourservice.com/premium-data \ -H "Authorization: L402 :" ``` [Contact Sales →](mailto:support@lightningenable.com) --- ## Website Placement Lightning Enable products are sold through different channels: ### lightningenable.com - **Free Producer Sandbox**, **Agentic Commerce**, and **Agentic Commerce — Business** (contact us) plans - Platform integrations (Shopify Commerce) included with any paid plan This targets the emerging AI/agent economy where machines need to pay for API access programmatically, as well as e-commerce platforms that want to accept Bitcoin Lightning payments. --- ## Pricing Philosophy **No per-transaction fees.** Your subscription is based on capabilities, not usage. | What We Charge For | What We Don't Charge For | |--------------------|-----------------------| | API access | Transaction volume | | Feature tier | Payment amounts | | Support level | Number of invoices | | Integration depth | API calls | Your payment provider may charge their own processing fees.* These go to the provider, not to us. *Check your provider's current fee schedule for details. --- ## Subscription Enforcement All plans are enforced via middleware on every API request. If your subscription expires, is canceled, or your payment fails, API requests return `403 Forbidden` with a clear error message and recommended action. Key behaviors: - **Active** and **trialing** subscriptions have full API access - **Past due**, **canceled**, and **unpaid** subscriptions are blocked immediately - **Billing period validation** catches expired subscriptions even before Stripe webhooks arrive - **Feature gating** restricts plan-specific endpoints (e.g., L402 server-side features require an Agentic Commerce plan) - **Free Producer Sandbox accounts** have API access without a subscription, capped at 3 endpoints and 200 challenges per month For complete details, see [Subscription & Plan Enforcement](/products/subscription-management). --- ## Getting Started 1. **Choose your product** based on your platform and needs 2. **Subscribe** — Agentic Commerce via self-serve Stripe checkout; Agentic Commerce — Business by contacting support@lightningenable.com 3. **Set up your payment provider** — [Strike](/strike-setup/account-setup) (recommended) or [OpenNode](/opennode-setup/account-setup) 4. **Configure your API key** in the [dashboard](https://api.lightningenable.com/dashboard) → Settings 5. **Integrate** using our docs and examples Ready to start? [Contact Sales](mailto:sales@refinedelement.com) or [Subscribe Now](https://api.lightningenable.com). ============================================================================== # Agent Discovery & Registry Source: https://docs.lightningenable.com/products/shopify-commerce/agent-discovery ============================================================================== # Agent Discovery & Registry When you create a Shopify integration with Lightning Enable, your store is **automatically registered** in the L402 API registry. This means AI agents can find your store using the `discover_api` MCP tool — no extra setup required. ## How It Works ``` AI Agent L402 Registry Your Store │ │ │ ├─ discover_api("coffee") ─────────►│ │ │◄──── matching stores ────────────┤ │ │ │ │ ├─ discover_api(url=manifest) ─────►│ │ │◄──── full endpoint details ──────┤ │ │ │ │ ├─ GET /catalog ────────────────────┼───────────────────────────────►│ │◄──── products ────────────────────┼───────────────────────────────┤ │ │ │ ├─ POST /checkout ──────────────────┼───────────────────────────────►│ │◄──── 402 + invoice ──────────────┤ │ │ ... (pay + claim flow) ... │ │ ``` 1. **Agent searches** the registry using keywords like "coffee", "health", or a category like "food-and-beverage" 2. **Registry returns** matching stores with name, description, categories, and manifest URL 3. **Agent fetches manifest** for full endpoint details — what's free, what requires payment, pricing model 4. **Agent uses the store** — browses catalog, creates checkout, pays, and claims ## Registry Listing By default, `listInRegistry` is enabled when you create a Shopify integration. Your store appears in the registry with: - **Name** — derived from your slug (e.g., "my-store (Shopify)") - **Description** — your `registryDescription` or a default description - **Categories** — your `registryCategories` for keyword and category search - **4 endpoints** — catalog (free), checkout (L402/dynamic), claim (L402), order status (free) ### Categories Categories help agents find your store. Use a JSON array of lowercase, hyphenated terms that describe what you sell: ```json ["commerce", "food-and-beverage", "health", "hydration"] ``` Good categories are specific and domain-appropriate. Some examples: | Store Type | Suggested Categories | |------------|---------------------| | Food & drink | `commerce`, `food-and-beverage`, `health`, `nutrition` | | Clothing | `commerce`, `apparel`, `fashion` | | Electronics | `commerce`, `electronics`, `gadgets` | | Art & prints | `commerce`, `art`, `digital-goods` | | Software licenses | `commerce`, `software`, `digital-goods` | ### Description The `registryDescription` is used for keyword search. Write it like you're describing your store to an AI agent that needs to decide whether to shop there: ``` Premium single-origin coffee beans and brewing equipment. Specialty roasts from Ethiopia, Colombia, and Guatemala available for AI agent purchases via Lightning L402 payments. ``` ## Configuring Registry Settings ### At Creation Include registry fields when creating your integration (`shopifyClientId` and `adminApiAccessToken` come from the custom app you create in [Setup Step 1](/products/shopify-commerce/setup#step-1-create-a-custom-app-for-your-store) — the secret starts with `shpss_`): ```bash curl -X POST https://api.lightningenable.com/api/merchant/shopify \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "slug": "my-coffee-store", "shopifyDomain": "my-store.myshopify.com", "shopifyClientId": "YOUR_CLIENT_ID", "adminApiAccessToken": "shpss_YOUR_CLIENT_SECRET", "listInRegistry": true, "registryCategories": "[\"commerce\",\"food-and-beverage\",\"coffee\"]", "registryDescription": "Premium single-origin coffee beans and brewing equipment available for AI agent purchases via Lightning L402 payments." }' ``` ### Updating Update registry fields at any time: ```bash curl -X PUT https://api.lightningenable.com/api/merchant/shopify \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "registryCategories": "[\"commerce\",\"food-and-beverage\",\"coffee\",\"specialty-roasts\"]", "registryDescription": "Updated description for your store." }' ``` ### Configuration Reference | Field | Type | Default | Description | |-------|------|---------|-------------| | `listInRegistry` | boolean | `true` | Whether to list your store in the L402 API registry | | `registryCategories` | string | `["commerce"]` | JSON array of categories for search and filtering | | `registryDescription` | string | auto-generated | Rich description for keyword search | ## Disabling Registry Listing To remove your store from agent discovery while keeping the integration active: ```bash curl -X PUT https://api.lightningenable.com/api/merchant/shopify \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"listInRegistry": false}' ``` Your store endpoints still work — agents with the direct URL can still purchase. They just won't find you via `discover_api` search. :::info Pausing vs Delisting Setting `isActive: false` **pauses** the integration entirely (no purchases possible) and also removes it from the registry. Setting `listInRegistry: false` only hides it from discovery — agents with the URL can still shop. ::: ## The Manifest Each registered store gets an auto-generated L402 manifest at: ``` https://api.lightningenable.com/l402/proxy/shopify-{slug}/.well-known/l402-manifest.json ``` The manifest follows the [L402 Manifest Schema](/schemas/l402-manifest-v1.json) and includes: - **Service metadata** — name, description, categories, documentation URL - **L402 configuration** — payment flow, supported currencies - **Endpoints** — each with path, method, pricing model, and whether L402 is required ### Endpoint Pricing Models | Endpoint | Pricing | L402 Required | Description | |----------|---------|---------------|-------------| | `GET /catalog` | Free | No | Browse products — always free | | `POST /checkout` | Dynamic | Yes | Price depends on cart contents | | `POST /claim` | Free | Yes | L402 credential required (from checkout payment) | | `GET /orders/{id}` | Free | No | Check order status with claim token | "Dynamic" pricing means the price is determined at request time based on what's in the cart. The agent sees this in the manifest and knows to expect a variable amount. ## How Agents Use discover_api AI agents using the [Lightning Enable MCP server](/products/agentic-commerce/mcp-quickstart) can search the registry in two ways: ### Keyword Search ``` Agent: "Find stores that sell coffee" → discover_api(query="coffee") → Returns matching stores with names, descriptions, categories, and manifest URLs ``` ### Category Browse ``` Agent: "What food and beverage stores are available?" → discover_api(category="food-and-beverage") → Returns all stores in that category ``` ### Manifest Fetch ``` Agent: "Show me the full details for this store" → discover_api(url="https://api.lightningenable.com/l402/proxy/shopify-my-store/.well-known/l402-manifest.json") → Returns complete endpoint details, pricing models, and L402 requirements ``` ### Budget-Aware Discovery With `budgetAware: true` (the default), discover_api annotates results with how many calls the agent can afford based on its remaining budget. For Shopify stores with free catalog browsing, this means agents know they can browse unlimited catalogs at zero cost. ## Next Steps - [Setup Guide](/products/shopify-commerce/setup) — Create your Shopify integration - [API Reference](/products/shopify-commerce/api-reference) — Full endpoint documentation - [MCP Quick Start](/products/agentic-commerce/mcp-quickstart) — Set up AI agents to discover and purchase ============================================================================== # API Reference Source: https://docs.lightningenable.com/products/shopify-commerce/api-reference ============================================================================== # Shopify API Reference All public store endpoints are unauthenticated — L402 payment proof serves as authorization. Admin endpoints require your Lightning Enable API key. **Base URL:** `https://api.lightningenable.com` --- ## Public Endpoints These endpoints are accessible without an API key. The `{slug}` parameter is the URL-safe identifier you chose when creating the integration. ### Get Catalog Fetches the product catalog from your Shopify store. Results are cached. ``` GET /api/shopify/{slug}/catalog ``` **Response: `200 OK`** ```json { "storeName": "your-store.com", "slug": "your-store", "shipping": { "domesticUsd": 5.99, "domesticOnly": true, "freeShippingEnabled": true, "freeShippingThresholdUsd": 50.00 }, "products": [ { "productId": 8234567890123, "title": "Premium Coffee Beans", "description": "Single-origin Ethiopian coffee, light roast", "imageUrl": "https://cdn.shopify.com/s/files/...", "variants": [ { "variantId": 44567890123456, "title": "12oz Bag", "priceUsd": 18.99, "available": true }, { "variantId": 44567890123457, "title": "2lb Bag", "priceUsd": 34.99, "available": true } ] } ] } ``` **Error Responses:** | Status | Meaning | |--------|---------| | `404` | Integration not found or inactive | --- ### Create Checkout Creates an L402 checkout with a Lightning invoice. Returns HTTP 402 with the payment challenge. ``` POST /api/shopify/{slug}/checkout ``` **Headers (required):** | Header | Format | Description | |--------|--------|-------------| | `X-Buyer-Location` | `{country}-{state}-{zip}` | **Required.** The buyer's location for destination-based tax calculation (e.g., `US-FL-34787`). There is no fallback — a missing or blank header returns `400`. Pass the buyer's actual country-state-zip, never the seller's. | **Request Body:** ```json { "items": [ { "variantId": 44567890123456, "quantity": 2 }, { "variantId": 44567890123457, "quantity": 1 } ], "email": "customer@example.com" } ``` | Field | Required | Description | |-------|----------|-------------| | `items` | Yes | Array of `{ variantId, quantity }` (1–10 items) | | `email` | No | Buyer email captured at checkout time. When set and the merchant's payment provider fires a paid webhook (Strike, OpenNode), the buyer is automatically sent the claim URL once the payment lands. **This is the recovery channel when the buyer's wallet returns no preimage** — e.g., Strike-to-Strike payments, where the agent can't complete the L402 retry below. Agents buying on behalf of a human should collect and pass this; agents acting alone can omit it and rely on the L402 retry path. | **Validation Rules:** - 1–10 items per checkout - Each item quantity: 1–10 - Total quantity across all items: max 10 - All variant IDs must exist in the catalog - All variants must be available (in stock) **Response: `402 Payment Required`** The response includes a `WWW-Authenticate` header with the L402 challenge: ``` WWW-Authenticate: L402 macaroon="...", invoice="lnbc..." ``` **Response Body:** ```json { "orderId": "shpfy_a1b2c3d4e5f6", "items": [ { "variantId": 44567890123456, "productTitle": "Premium Coffee Beans", "variantTitle": "12oz Bag", "quantity": 2, "priceUsd": 18.99 }, { "variantId": 44567890123457, "productTitle": "Premium Coffee Beans", "variantTitle": "2lb Bag", "quantity": 1, "priceUsd": 34.99 } ], "subtotalUsd": 72.97, "shippingUsd": 0.00, "taxUsd": 5.11, "taxNote": null, "totalUsd": 78.08, "totalSats": 77200, "claimExpiresAt": "2026-03-05T18:30:00Z", "invoice": "lnbc721500n1pn...", "macaroonBase64": "AgELbGlnaHRuaW5n...", "paymentHash": "a1b2c3d4e5f6...", "invoiceExpiresAt": "2026-02-26T18:40:00Z" } ``` **Key Fields:** - `invoice` — BOLT11 Lightning invoice to pay - `macaroonBase64` — L402 macaroon (save this for the post-payment retry and claim steps) - `paymentHash` — links the macaroon to the invoice - `claimExpiresAt` — deadline for claiming the order after payment (configurable window, default 30 days) - `totalSats` — BTC amount locked at current exchange rate - `taxUsd` — tax amount calculated via Shopify's Draft Order API from the buyer's `X-Buyer-Location` - `taxNote` — always `null`. (Tax is always computed from the buyer's location; there is no estimation path, so there is nothing to annotate.) - `totalUsd` — total including subtotal + shipping + tax :::info No claim token in the 402 response — by design The checkout response deliberately does **not** include a claim token or claim page URL. They are credentials for the post-payment claim flow — emitting them before payment would let an observer race the legitimate buyer to claim the order once the payment lands. To obtain them, prove payment via the [checkout retry](#complete-a-paid-checkout-checkout-retry) below. ::: **Tax Calculation:** Tax is calculated at checkout using Shopify's Draft Order API. A temporary draft order is created with the cart items and a partial address (derived from the buyer location), Shopify computes the tax, the amount is read, and the draft is deleted. The tax is included in the Lightning invoice total. The buyer location comes **only** from the `X-Buyer-Location` request header (format: `{country}-{state}-{zip}`, e.g., `US-FL-34787`). The header is **required** — there is no fallback to a stored default, because computing tax for the wrong jurisdiction (e.g., the seller's address) would be incorrect. If the header is missing or blank, checkout returns a `400` error. **Error Responses:** | Status | Meaning | |--------|---------| | `400` | Invalid cart (bad variant ID, unavailable item, exceeds limits), missing/blank `X-Buyer-Location`, or a payment credential for a disabled protocol | | `404` | Integration not found | | `503` | Bitcoin price feed temporarily unavailable — all price sources failed, so no invoice is issued at a stale rate. Response includes a `correlationId`. **Retryable** — retry shortly. | | `503` | Tax calculation failed (Shopify Draft Order API error or missing Admin API token). **Retryable.** | --- ### Complete a Paid Checkout (Checkout Retry) After paying the invoice, **re-POST the same checkout endpoint** with your L402 payment proof in the `Authorization` header. This is the standard L402 client pattern (retry the same URL after payment) and it's how you obtain the claim credentials that are deliberately withheld from the 402 response. ``` POST /api/shopify/{slug}/checkout Authorization: L402 {macaroonBase64}:{preimageHex} ``` Re-send the **same body you sent on the first checkout**. The body must be valid (1–10 items) — `[ApiController]` model validation runs before the credential is inspected, so an empty `{"items": []}` is rejected with `400` even with a valid `Authorization` header. Its *content* does not affect the retry, though: the payment credential identifies the paid order (by macaroon, or by `SHA256(preimage) == paymentHash` when no macaroon is sent). The simplest approach is to keep the original checkout body in a variable and reuse it. **Response: `200 OK`** ```json { "orderId": "shpfy_a1b2c3d4e5f6", "status": "PaidAwaitingDetails", "shopifyOrderNumber": null, "claimPageUrl": "https://your-store.com/pages/claim-your-order?token=SC-x7k9m2p4", "message": "Payment verified. Submit shipping details to complete your order." } ``` - `claimPageUrl` — the merchant's claim page with the claim token in the `?token=` query parameter. Present only when the merchant has configured a claim page; otherwise `null`. Hand this URL to the human buyer, or extract the token to call [Claim Order](#claim-order) directly. - If the buyer's wallet doesn't return a preimage (e.g., some Strike-to-Strike payments), this retry isn't possible — that's what the optional `email` field at checkout is for: the claim URL is sent to the buyer automatically once the payment lands. **Error Responses:** | Status | Meaning | |--------|---------| | `400` | Invalid request body (empty/`items: []` or more than 10 items — validated before the credential), credential for a disabled protocol, or invalid preimage format | | `401` | L402 verification failed (macaroon/preimage don't verify) | | `404` | No order found for this payment token | --- ### Claim Order Claims an order after payment: verifies payment, saves shipping details, and creates a Shopify order. ``` POST /api/shopify/{slug}/claim ``` **Headers:** ``` Authorization: L402 {macaroonBase64}:{preimageHex} Content-Type: application/json ``` The `Authorization` header format is `L402 :`: - `macaroon` — the `macaroonBase64` from the checkout response - `preimage` — the 64-character hex preimage obtained after paying the invoice **When is the `Authorization` header required?** Only for orders still in `PendingPayment` — the preimage is what proves payment and transitions the order to paid. For orders already marked paid (`PaidAwaitingDetails`, e.g. after the [checkout retry](#complete-a-paid-checkout-checkout-retry) or a provider webhook), the claim token alone is sufficient — this is what lets the merchant's claim page work from a plain browser without L402 headers. **Request Body:** ```json { "claimToken": "SC-x7k9m2p4", "email": "customer@example.com", "shippingAddress": { "firstName": "Jane", "lastName": "Doe", "address1": "123 Main St", "address2": "Apt 4B", "city": "Austin", "province": "TX", "zip": "78701", "country": "US", "phone": "+15125551234" } } ``` **Shipping Address Fields:** | Field | Required | Max Length | |-------|----------|-----------| | `firstName` | Yes | 200 | | `lastName` | Yes | 200 | | `address1` | Yes | 500 | | `address2` | No | 500 | | `city` | Yes | 100 | | `province` | No | 100 | | `zip` | No | 20 | | `country` | Yes | 100 | | `phone` | No | 30 | **Response: `200 OK`** ```json { "orderId": "shpfy_a1b2c3d4e5f6", "status": "PaidWithDetails", "shopifyOrderNumber": "#1042", "claimPageUrl": "https://your-store.com/pages/claim-your-order?token=SC-x7k9m2p4", "message": "Order #1042 created successfully. You'll receive shipping confirmation at customer@example.com." } ``` **Error Responses:** | Status | Meaning | |--------|---------| | `400` | Expired claim window, already claimed, missing payment proof, invalid preimage format, **failed L402 verification**, or international address on a domestic-only store | | `401` | Payment credential for a disabled protocol only (a failing macaroon/preimage returns `400`, not `401`) | | `404` | Unknown claim token (or token belongs to a different store) | --- ### Get Order Status Check the status of an order. Requires the claim token as a query parameter. ``` GET /api/shopify/{slug}/orders/{orderId}?claimToken={claimToken} ``` The claim token is single-use for **claiming**, but it stays valid as a **read credential** after a successful claim — status lookups with the same token keep working for claimed orders (`PaidWithDetails`, `Fulfilled`, `Shipped`), so the buyer can track their order through to delivery. Claimed orders remain queryable with the claim token for **90 days after claiming**, after which they are purged. For orders that were never claimed, status lookups stop working once the claim window expires. **Response: `200 OK`** ```json { "orderId": "shpfy_a1b2c3d4e5f6", "status": "Shipped", "totalUsd": 72.97, "paidSats": 72150, "shopifyOrderNumber": "#1042", "trackingNumber": "1Z999AA10123456784", "trackingCarrier": "UPS", "trackingUrl": "https://www.ups.com/track?tracknum=1Z999AA10123456784", "createdAt": "2026-02-26T18:30:00Z", "paidAt": "2026-02-26T18:31:15Z", "shippedAt": "2026-02-28T14:22:00Z" } ``` **Error Responses:** | Status | Meaning | |--------|---------| | `404` | Order not found, claim token doesn't match, or claim window expired before the order was claimed. A token mismatch is deliberately a uniform `404` (never `401`) to prevent order-ID enumeration. | --- ### Get llms.txt Manifest Returns a plain-text `llms.txt` manifest for the store — tagline, L402 endpoints, products, shipping rules, and links, formatted for AI agent discovery. Merchants can redirect `/llms.txt` on their own domain to this endpoint. ``` GET /api/shopify/{slug}/llms.txt HEAD /api/shopify/{slug}/llms.txt ``` **Response: `200 OK`** — `text/plain; charset=utf-8`. Cached with the same TTL as the catalog. **Error Responses:** | Status | Meaning | |--------|---------| | `404` | Integration not found or inactive (also `text/plain`) | --- ## Merchant Admin Endpoints These endpoints require your Lightning Enable API key in the `X-API-Key` header. ### Get Integration ``` GET /api/merchant/shopify ``` Returns your Shopify integration configuration. The Admin API token is never returned — only `hasAdminApiToken: true/false`. ### Create Integration ``` POST /api/merchant/shopify ``` See [Setup Guide](/products/shopify-commerce/setup#step-5-create-the-shopify-integration) for the full request schema. ### Update Integration ``` PUT /api/merchant/shopify ``` Partial update — include only the fields you want to change. ```json { "domesticShippingUsd": 6.99, "freeShippingEnabled": true, "defaultTaxLocation": "US-FL-34787", "isActive": false, "listInRegistry": true, "registryCategories": "[\"commerce\",\"food-and-beverage\"]", "registryDescription": "Your store description for agent discovery." } ``` ### Invalidate Cache ``` POST /api/merchant/shopify/invalidate-cache ``` Forces a refresh of the cached product catalog on the next request. ### Refresh Shop Info ``` POST /api/merchant/shopify/refresh-shop-info ``` Refreshes the integration's canonical store identity (`shopifyDomain` and the internal `*.myshopify.com` handle) from Shopify's `shop.json`. Use after changing your primary domain in Shopify. Returns the updated integration on success; `400` if no access token is configured; `502` if Shopify doesn't return shop info (e.g., revoked token — the response body includes the upstream status). ### Get Stats ``` GET /api/merchant/shopify/stats ``` Returns checkout conversion stats for every Shopify integration you own — scoped to your merchant account. `total` is the roll-up; `byStore` has one entry per store. **Response:** ```json { "merchantId": 42, "total": { "totalIntegrations": 1, "totalCheckouts": 120, "paidOrders": 18, "conversionRate": 0.15, "totalPaidSats": 1450000, "totalPaidUsd": 1312.50, "fulfilled": 16, "shipped": 14, "firstOrderAt": "2026-03-01T10:00:00Z", "lastOrderAt": "2026-06-30T21:45:00Z" }, "byStore": [ { "slug": "my-store", "shopifyDomain": "your-store.com", "totalCheckouts": 120, "paidOrders": 18, "conversionRate": 0.15, "totalPaidSats": 1450000, "totalPaidUsd": 1312.50, "fulfilled": 16, "shipped": 14, "firstOrderAt": "2026-03-01T10:00:00Z", "lastOrderAt": "2026-06-30T21:45:00Z" } ] } ``` ### List Orders ``` GET /api/merchant/shopify/orders GET /api/merchant/shopify/orders?status=PaidWithDetails GET /api/merchant/shopify/orders?page=2&pageSize=10 ``` **Query Parameters:** | Parameter | Default | Description | |-----------|---------|-------------| | `status` | (all) | Filter by status: `PendingPayment`, `PaidAwaitingDetails`, `PaidWithDetails`, `Fulfilled`, `Shipped` | | `page` | 1 | Page number | | `pageSize` | 20 | Results per page | **Response:** ```json [ { "orderId": "shpfy_a1b2c3d4e5f6", "status": "PaidWithDetails", "totalUsd": 72.97, "paidSats": 72150, "email": "customer@example.com", "shopifyOrderNumber": "#1042", "createdAt": "2026-02-26T18:30:00Z" } ] ``` --- ## Complete Purchase Flow Example Here's the full flow using `curl`: ```bash # 1. Browse products CATALOG=$(curl -s https://api.lightningenable.com/api/shopify/my-store/catalog) echo "$CATALOG" | jq '.products[0].variants[0]' # 2. Create checkout (get variant ID from catalog) # X-Buyer-Location is REQUIRED for tax calculation — pass the buyer's country-state-zip # email is optional but recommended when buying for a human — it's the recovery # channel if your wallet doesn't return a preimage # Keep the body in a variable — you re-send the SAME body on the retry (step 4). CHECKOUT_BODY='{"items": [{"variantId": 44567890123456, "quantity": 1}], "email": "customer@example.com"}' CHECKOUT=$(curl -s -X POST https://api.lightningenable.com/api/shopify/my-store/checkout \ -H "Content-Type: application/json" \ -H "X-Buyer-Location: US-FL-34787" \ -d "$CHECKOUT_BODY") # Extract the invoice and macaroon (there is NO claimToken in the 402 response — by design) INVOICE=$(echo "$CHECKOUT" | jq -r '.invoice') MACAROON=$(echo "$CHECKOUT" | jq -r '.macaroonBase64') ORDER_ID=$(echo "$CHECKOUT" | jq -r '.orderId') # 3. Pay the invoice (using Lightning Enable MCP, lncli, or any Lightning wallet) # This gives you the preimage (64 hex chars) PREIMAGE="your_preimage_hex_here" # 4. Retry the SAME checkout endpoint with your payment proof. # Re-send the ORIGINAL checkout body — it must be valid (1–10 items), because # model validation runs before the L402 credential is inspected (an empty # {"items": []} would 400). Its content doesn't affect the retry; the credential # identifies the paid order. This verifies payment and returns the claim page URL. RETRY=$(curl -s -X POST https://api.lightningenable.com/api/shopify/my-store/checkout \ -H "Content-Type: application/json" \ -H "Authorization: L402 ${MACAROON}:${PREIMAGE}" \ -d "$CHECKOUT_BODY") # claimPageUrl carries the claim token as ?token=... (when the merchant has a claim page). # Hand the URL to the human buyer, or extract the token to claim via the API: CLAIM_PAGE_URL=$(echo "$RETRY" | jq -r '.claimPageUrl') CLAIM_TOKEN="${CLAIM_PAGE_URL##*token=}" # 5. Claim the order (the order is already marked paid, so the token alone suffices — # including the Authorization header again is harmless) curl -X POST https://api.lightningenable.com/api/shopify/my-store/claim \ -H "Content-Type: application/json" \ -d "{ \"claimToken\": \"${CLAIM_TOKEN}\", \"email\": \"customer@example.com\", \"shippingAddress\": { \"firstName\": \"Jane\", \"lastName\": \"Doe\", \"address1\": \"123 Main St\", \"city\": \"Austin\", \"province\": \"TX\", \"zip\": \"78701\", \"country\": \"US\" } }" # 6. Track the order — the claim token keeps working as a read credential after the claim curl "https://api.lightningenable.com/api/shopify/my-store/orders/${ORDER_ID}?claimToken=${CLAIM_TOKEN}" ``` --- ## Edge Cases | Scenario | What Happens | |----------|-------------| | **BTC price moves after checkout** | The sats amount is locked in the Lightning invoice at checkout time. The invoice expires in ~10 minutes. | | **Product goes out of stock** | Checked at checkout time against cached catalog. At claim time, Shopify's `decrement_obeying_policy` handles inventory. | | **Claim token used twice** | Second claim is rejected (`400`, already claimed) — tokens are single-use for claiming. The token remains valid for order-status lookups. | | **Wallet returns no preimage** | The L402 checkout retry isn't possible (e.g., some Strike-to-Strike payments). If `email` was provided at checkout, the buyer receives the claim URL automatically once the payment lands — otherwise contact the merchant. | | **Shopify order creation fails** | Order stays in `PaidAwaitingDetails`. Payment is safe. Can be retried via admin. | | **BTC price feed unavailable** | Checkout returns `503` with a `correlationId` rather than quoting a stale rate. Retry shortly. | | **No tax location provided** | Checkout returns `400` if the required `X-Buyer-Location` header is missing or blank. There is no fallback. | | **Invoice expires before payment** | Order stays in `PendingPayment`. Agent must create a new checkout. | | **Claim window expires before claiming** | Contact the merchant for manual resolution. Payment is recorded. Default window is 30 days (configurable 1–365). | ============================================================================== # Overview Source: https://docs.lightningenable.com/products/shopify-commerce/overview ============================================================================== # Shopify Commerce :::tip Subscription Required Shopify L402 integration requires an **Agentic Commerce** subscription — either **Agentic Commerce** ($49/month) or **Business** ([contact us](mailto:support@lightningenable.com)). [View pricing](/products/product-overview) ::: Lightning Enable's Shopify integration lets **AI agents purchase products** from your Shopify store using Lightning Network payments. Your existing Shopify catalog, inventory management, and fulfillment workflows stay exactly as they are — Lightning Enable adds a new sales channel powered by the L402 protocol. ## How It Works ``` AI Agent Lightning Enable Your Shopify Store │ │ │ ├─ GET /catalog ──────────────────►│──── Admin API products ────────────►│ │◄──── product list ──────────────┤◄──── products + prices ─────────────┤ │ │ │ ├─ POST /checkout ────────────────►│ │ │ (items + buyer location) │── create Lightning invoice ──► │ │◄──── 402 + invoice ─────────────┤ (via Strike/OpenNode) │ │ │ │ ├─ pay invoice ───────────────────►│ │ │◄──── preimage (proof) ──────────┤ │ │ │ │ ├─ POST /checkout (retry) ────────►│ │ │ (L402 payment proof) │ │ │◄──── claim URL + token ─────────┤ │ │ │ │ ├─ POST /claim ───────────────────►│ │ │ (claim token + shipping) │── POST /admin/api/orders.json ────►│ │◄──── order confirmation ────────┤◄──── Shopify order created ────────┤ │ │ (paid, inventory decremented) │ ``` 1. **Browse** — Agent fetches your product catalog. Lightning Enable pulls it from the Shopify Admin API using your app credentials (the public `products.json` endpoint is only a fallback when no token is configured) and caches it. 2. **Checkout** — Agent submits a cart with the buyer's location (required for tax), receives a Lightning invoice 3. **Pay** — Agent pays the invoice, receives cryptographic proof of payment (preimage) 4. **Retry** — Agent re-POSTs the same checkout endpoint with the L402 proof, receives the claim URL carrying the claim token (withheld from the pre-payment response so nobody can race the buyer to claim) 5. **Claim** — Agent or human provides shipping details with the claim token, Shopify order is created ## Key Features - **Zero changes to your Shopify store** — products, inventory, and fulfillment stay in Shopify - **Auto-registered for agent discovery** — your store appears in the L402 API registry so AI agents can find it via `discover_api` - **Server-side pricing** — prices are always fetched from your Shopify catalog, never client-controlled - **Cryptographic payment proof** — L402 verification (SHA256(preimage) == payment_hash) confirms payment without webhook dependencies - **Automatic Shopify orders** — orders appear in your Shopify admin as "paid" with inventory decremented - **Automatic tax calculation** — tax computed via Shopify's Draft Order API using buyer location, included in the Lightning invoice total - **Flat-rate shipping** — configurable domestic and international rates with optional free shipping threshold - **Catalog caching** — products cached for configurable duration (default 15 minutes) to minimize API calls - **Claim tokens** — configurable claim window (default 30 days) for providing shipping details after payment ## What You Need 1. **Active Shopify store** — products you want to sell via L402 need **Active** status (publishing to the Online Store sales channel is not required; the Admin API catalog fetch doesn't filter by sales channel) 2. **Shopify app credentials** with `write_orders`, `write_draft_orders`, and `read_products` scopes — the catalog is fetched via the Shopify Admin API using these credentials (the public `products.json` endpoint is used only as a fallback when no token is configured) 3. **Lightning Enable subscription** — Agentic Commerce ($49/mo) or Business ([contact us](mailto:support@lightningenable.com)) 4. **Payment provider account** — [Strike](https://dashboard.strike.me) (recommended) or OpenNode ## Agent Flow vs Human Flow The Shopify L402 integration supports a two-phase flow: | Phase | Who | What Happens | |-------|-----|-------------| | **Purchase** | AI Agent | Browses catalog, creates checkout, pays Lightning invoice | | **Claim** | AI Agent or Human | Provides shipping address and email, triggers Shopify order creation | The **claim token** bridges the two phases. It is issued only after payment is proven — via the checkout retry with the L402 preimage, or delivered to the buyer's email when one was provided at checkout. The agent (or a human) then provides shipping details using the token within the configured claim window (default 30 days). After a successful claim, the same token keeps working as a read credential for order-status tracking. ## Order Lifecycle | Status | Meaning | |--------|---------| | `PendingPayment` | Checkout created, waiting for Lightning payment | | `PaidAwaitingDetails` | Payment confirmed, waiting for shipping details | | `PaidWithDetails` | Shipping details provided, Shopify order created | | `Fulfilled` | Shopify fulfillment started | | `Shipped` | Tracking info available | ## Next Steps - [Setup Guide](/products/shopify-commerce/setup) — Step-by-step integration setup - [API Reference](/products/shopify-commerce/api-reference) — Full endpoint documentation - [Agent Discovery & Registry](/products/shopify-commerce/agent-discovery) — Control how agents find your store ============================================================================== # Setup Guide Source: https://docs.lightningenable.com/products/shopify-commerce/setup ============================================================================== # Shopify Setup Guide This guide walks you through connecting your Shopify store to Lightning Enable for L402 agentic commerce. Setup takes about 10 minutes. ## Step 1: Create a Custom App for Your Store Lightning Enable uses Shopify's OAuth client-credentials flow to securely create orders in your store after Lightning payment. You create a custom app in your Shopify store admin (via the Dev Dashboard, which is Shopify's current workflow for merchant-created apps) and connect it to Lightning Enable using the app's Client ID and Client Secret. :::info Coming soon: one-click install from the Shopify App Store Lightning Enable is in review for listing in the Shopify App Store. Once approved, you'll install it directly from the App Store with a single click and this step goes away. Until then, every merchant creates their own custom app using the steps below — Shopify's preferred path for single-store integrations. ::: :::note Dev Dashboard vs Partner Dashboard You do **not** need a Shopify Partners account for this. The Dev Dashboard is launched from inside your own store admin (Settings → Apps and sales channels → Develop apps → **Build apps**) and is the current path for merchants creating an app for their own store. Partners Dashboard is a separate product for agencies and developers building apps that distribute to other merchants — that's not what you want here. ::: 1. Sign in to your Shopify store admin 2. Go to **Settings → Apps and sales channels** 3. Click **Develop apps** 4. Click **Build apps** (this opens the Shopify Dev Dashboard) 5. In the Dev Dashboard, click **Create application** 6. Name the app `Lightning Enable` (or anything you prefer) 7. Under **Configuration → API scopes**, add the following Admin API access scopes: | Scope | Purpose | |-------|---------| | `write_orders` | Create orders after Lightning payment | | `write_draft_orders` | Create temporary draft orders for tax calculation | | `read_products` | Verify product availability at claim time | | `read_inventory` | Check stock levels | 8. Click **Save** on the scope configuration 9. Go to the app's **Home** tab in the Dev Dashboard and click **Install app** → select your store → **Install** 10. After install, open the app's **API credentials** (or **Overview**) panel and copy: - **Client ID** - **Client Secret** (starts with `shpss_`) — Shopify reveals this value one time on this screen Keep this tab open — you'll paste both values into Lightning Enable in Step 5. :::caution Save Your Client Secret Shopify reveals the Client Secret only once on the API credentials screen after the app is installed. If you lose it, you can regenerate it from the same screen, but you'll need to update it in Lightning Enable afterward — the old secret stops working immediately on regeneration. ::: :::tip If your store already has a legacy custom app Shopify stopped allowing creation of new legacy custom apps in January 2026, but existing legacy custom apps still work. If you created yours before the cutoff, it's under Settings → Apps and sales channels → Develop apps (with no "Build apps" step), and you can use its existing Client ID and Client Secret. If you have to re-create for any reason, you'll go through the Dev Dashboard flow above. ::: ## Step 2: Verify Your Products Are Ready Lightning Enable fetches your catalog from the Shopify Admin API using the app credentials from Step 1. Make sure the products you want available via L402 are set up correctly: 1. Go to **Products** in Shopify admin 2. For each product you want available via L402: - Status should be **Active** — the Admin API catalog fetch filters to active products - Confirm inventory and pricing are accurate - Publishing to **Online Store** is recommended but not strictly required — Lightning Enable uses the Admin API, which doesn't filter by sales channel. That said, keeping Online Store checked keeps your catalog consistent with your customer-facing storefront and makes the optional check below work. **Optional sanity check** — if you want to verify your products are also visible on Shopify's public storefront, open this URL in your browser (replace with your domain): ``` https://your-store.com/products.json ``` If it returns your products, they're published to the Online Store channel. An empty response only means products aren't published to Online Store — Lightning Enable's Admin API catalog fetch can still work regardless. ## Step 3: Sign Up for Lightning Enable If you don't already have a Lightning Enable account: 1. Visit [lightningenable.com](https://lightningenable.com) and sign up for an **Agentic Commerce** plan — either **Agentic Commerce** ($49/month) or **Business** ([contact us](mailto:support@lightningenable.com)) 2. Complete the Stripe checkout 3. **Save your API key** from the confirmation page — you'll need it for the next step :::info Already Have a Subscription? If you're on a plan that doesn't include L402/Agentic Commerce, upgrade through the [merchant dashboard](https://api.lightningenable.com/dashboard) or contact support. ::: ## Step 4: Configure Your Payment Provider You need a payment provider to handle Lightning invoice creation and settlement. ### Option A: Strike (Recommended) 1. Create a [Strike account](https://dashboard.strike.me) 2. Generate an API key in the Strike dashboard with the `partner.receive-request.create` scope (required for creating Lightning invoices) 3. Configure it in Lightning Enable: ```bash # Save your Strike API key (this also defaults you to the Strike provider on first save) curl -X PUT https://api.lightningenable.com/api/merchant/strike-key \ -H "X-API-Key: YOUR_LIGHTNING_ENABLE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"strikeApiKey": "YOUR_STRIKE_API_KEY"}' # Optional: explicitly set the payment provider (provider is a string, not an int) curl -X PUT https://api.lightningenable.com/api/merchant/payment-provider \ -H "X-API-Key: YOUR_LIGHTNING_ENABLE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"provider": "strike"}' ``` Strike supports preimage extraction, which is required for L402 payment verification. ### Option B: OpenNode See the [OpenNode Setup Guide](/opennode-setup/account-setup) for configuration details. :::warning L402 Compatibility OpenNode does **not** return preimages for incoming payments. For Shopify L402 integration, **Strike is strongly recommended** as it provides the preimage needed for cryptographic payment verification. ::: ## Step 5: Create the Shopify Integration With your Lightning Enable API key, create the integration using the Client ID and Client Secret you copied in Step 1: ```bash curl -X POST https://api.lightningenable.com/api/merchant/shopify \ -H "X-API-Key: YOUR_LIGHTNING_ENABLE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "slug": "your-store-name", "shopifyDomain": "your-store.com", "shopifyClientId": "YOUR_CLIENT_ID", "adminApiAccessToken": "shpss_YOUR_CLIENT_SECRET", "domesticShippingUsd": 5.99, "internationalShippingUsd": 14.99, "freeShippingEnabled": false, "freeShippingThresholdUsd": 50.00, "catalogCacheTtlMinutes": 15, "defaultTaxLocation": "US-FL-34787", "registryCategories": "[\"commerce\",\"food-and-beverage\",\"coffee\"]", "registryDescription": "Premium coffee beans available for AI agent purchases via Lightning L402 payments." }' ``` Lightning Enable exchanges your Client ID and Client Secret with Shopify for a short-lived access token (valid ~24 hours), then reuses that token for Admin API calls until it needs to be refreshed. You don't need to rotate or refresh tokens manually — Lightning Enable handles that automatically and transparently. ### Configuration Options | Field | Required | Description | |-------|----------|-------------| | `slug` | Yes | URL-safe identifier for your store (lowercase, hyphens OK). Used in endpoint URLs: `/api/shopify/{slug}/catalog` | | `shopifyDomain` | Yes | Your Shopify store domain (e.g., `your-store.com` or `your-store.myshopify.com`). On save, Lightning Enable calls Shopify's `shop.json` and auto-corrects this to your customer-facing primary domain — your customers see `your-store.com`, your canonical `*.myshopify.com` handle is captured separately and managed for you. You can refresh both at any time from the dashboard. | | `shopifyClientId` | Yes | Shopify app **Client ID** from Step 1 | | `adminApiAccessToken` | Yes | Shopify app **Client Secret** (starts with `shpss_`) from Step 1 | | `domesticShippingUsd` | No | US flat-rate shipping (default: $5.99) | | `internationalShippingUsd` | No | International flat-rate shipping (default: $14.99) | | `freeShippingEnabled` | No | Enable free shipping above threshold (default: false) | | `freeShippingThresholdUsd` | No | Order subtotal for free shipping (default: $50.00) | | `catalogCacheTtlMinutes` | No | How long to cache products (default: 15 min, range: 1-1440) | | `domesticOnly` | No | **Default: `true`** — claims with a non-US shipping address are rejected until you flip this to `false`. Set it explicitly if you ship internationally. | | `claimExpiryDays` | No | Claim window after checkout, in days (default: 30, range: 1-365). Buyers must submit shipping details within this window. | | `allowedProductTypes` | No | Comma-separated list of Shopify `product_type` values to expose in the L402 catalog (case-insensitive). Empty = all active products. | | `claimPageUrl` | No | URL of your claim page (see Step 7). When set, post-payment responses include `claimPageUrl` with the claim token appended as `?token=...` — this is how buyers reach the shipping-details form. | | `defaultTaxLocation` | No | **Deprecated.** No longer read at checkout — tax always uses the **required** `X-Buyer-Location` header. Kept only for backward compatibility. | | `listInRegistry` | No | List in L402 API registry for agent discovery (default: true) | | `registryCategories` | No | JSON array of categories, e.g. `["commerce","food-and-beverage"]` | | `registryDescription` | No | Description for registry keyword search | ### Choosing a Slug Your slug determines the public URL for your store's endpoints: ``` https://api.lightningenable.com/api/shopify/{slug}/catalog https://api.lightningenable.com/api/shopify/{slug}/checkout https://api.lightningenable.com/api/shopify/{slug}/claim ``` Choose something short and memorable. It must be: - Lowercase letters, numbers, and hyphens only - Unique across all Lightning Enable merchants - Examples: `my-store`, `acme-goods`, `coffee-co` ## Step 6: Verify Your Setup ### Test the Catalog ```bash curl https://api.lightningenable.com/api/shopify/your-store-name/catalog ``` You should see your products with variants, prices, and shipping rules. No authentication is required for this endpoint. ### Test a Checkout ```bash # Use a real variant ID from the catalog response # X-Buyer-Location is REQUIRED for tax calculation — pass the buyer's country-state-zip curl -X POST https://api.lightningenable.com/api/shopify/your-store-name/checkout \ -H "Content-Type: application/json" \ -H "X-Buyer-Location: US-FL-34787" \ -d '{ "items": [ {"variantId": 12345678901234, "quantity": 1} ] }' ``` You should receive an HTTP 402 response with: - A Lightning invoice (BOLT11 string) - A macaroon (base64) - A payment hash - Order details with amounts in both USD and sats - Tax amount (`taxUsd`; `taxNote` is always `null`) :::note No claim token in the 402 response The claim token is deliberately **not** in the checkout response — exposing it pre-payment would let an observer race the buyer to claim the order. It's returned after you prove payment (step 2 below). ::: ### Test a Full Purchase To test the complete flow, use the [Lightning Enable MCP server](https://www.npmjs.com/package/lightning-enable-mcp): 1. Use the `pay_invoice` tool to pay the invoice from the checkout response 2. **Retry the same checkout endpoint** with your payment proof to verify payment and get the claim URL. Re-send the **same body** you used for the first checkout (the one from "Test a Checkout" above): ```bash curl -X POST https://api.lightningenable.com/api/shopify/your-store-name/checkout \ -H "Content-Type: application/json" \ -H "Authorization: L402 MACAROON_BASE64:PREIMAGE_HEX" \ -d '{"items": [{"variantId": 12345678901234, "quantity": 1}]}' ``` The body must be valid (1–10 items) — `[ApiController]` model validation runs before the credential is inspected, so an empty `{"items": []}` returns `400` even with a valid `Authorization` header. Its content doesn't affect the retry: the payment credential identifies the paid order. The `200` response includes `claimPageUrl` — your claim page (from Step 7) with the claim token appended as `?token=SC-xxxxxxxx`. 3. Claim the order — open `claimPageUrl` in a browser and submit the shipping form, or call the API directly with the token from the URL (the order is already marked paid, so the token alone is sufficient): ```bash curl -X POST https://api.lightningenable.com/api/shopify/your-store-name/claim \ -H "Content-Type: application/json" \ -d '{ "claimToken": "SC-xxxxxxxx", "email": "customer@example.com", "shippingAddress": { "firstName": "Jane", "lastName": "Doe", "address1": "123 Main St", "city": "Austin", "province": "TX", "zip": "78701", "country": "US" } }' ``` 4. Check your Shopify admin — a new order should appear, marked as **paid** 5. Verify order tracking works — the claim token remains valid for status lookups for **90 days after claiming**: ```bash curl "https://api.lightningenable.com/api/shopify/your-store-name/orders/ORDER_ID?claimToken=SC-xxxxxxxx" ``` ## Step 7: Add the Claim Page and `llms.txt` to Your Store After an AI agent pays the Lightning invoice, a human still needs to submit a shipping address. Lightning Enable provides a Shopify section + page template (compatible with Dawn and any OS 2.0 theme) and an `llms.txt` template to run this flow on your own domain so the buyer experience stays within your brand. The templates are provided when you start your integration: - **`le-claim.liquid`** — a configurable section that renders the shipping-details form after payment. Brand colors, copy, card layout, and typography are all set in the theme editor — no code edits per merchant. - **`page.claim.json`** — the page template that wires the section into a Shopify page. - **`llms-txt.liquid`** — a machine-readable page that tells AI agents how to browse and purchase from your store using L402. ### Install the claim section + page template 1. Shopify admin → **Online Store** → **Themes** → **Edit code** 2. Under **Sections**, click **Add a new section** → name it `le-claim` → choose **Liquid** → paste the contents of `sections/le-claim.liquid` 3. Under **Templates**, click **Add a new template** → type `page` → name it `claim` → choose **JSON** → paste the contents of `templates/page.claim.json` 4. Shopify admin → **Online Store** → **Pages** → **Add page**, title it `Claim Your Order`, leave the body empty, and under **Theme template** select `claim` → save and copy the page URL 5. In your [Lightning Enable dashboard](https://api.lightningenable.com/dashboard), set **Claim Page URL** to the page URL (e.g., `https://your-store.com/pages/claim-your-order`) 6. Open the page in the **theme editor** and click the **Lightning Enable Claim** section. Set the required **slug** (your Lightning Enable store slug from Step 5) and adjust brand colors, copy, and typography to match your theme. The default white card works on any background — leave it on unless your page already provides a contrasting container. ### Install the `llms.txt` template 1. Shopify admin → **Online Store** → **Themes** → **Edit code** 2. Under **Templates**, click **Add a new template** → type `page` → name it `llms-txt` 3. Paste the contents of `llms-txt.liquid` into the new template 4. In Shopify admin → **Online Store** → **Pages** → **Add page**, title it `llms`, leave the body empty, and under **Theme template** select `llms-txt` 5. Verify at `https://your-store.com/pages/llms` — you should see plain text (no site header or footer) :::tip Make `llms.txt` discoverable at your domain root AI agents typically look for `llms.txt` at `https://your-store.com/llms.txt`. Because Shopify serves custom pages under `/pages/`, add a URL redirect in Shopify admin (**Online Store** → **Navigation** → **URL Redirects**) from `/llms.txt` to `/pages/llms` so agents can find it at the canonical path. ::: ## Step 8: Go Live Once testing is complete, your L402 store endpoints are live and ready for AI agents. Your store is **automatically listed** in the L402 API registry, which means AI agents can find it using the `discover_api` MCP tool: ``` Agent: discover_api(query="coffee") → Your store appears in results with description, categories, and manifest URL ``` You can also share your catalog URL directly: ``` https://api.lightningenable.com/api/shopify/your-store-name/catalog ``` To manage your registry listing (categories, description, or opt out), see the [Agent Discovery & Registry](/products/shopify-commerce/agent-discovery) guide. ## Managing Your Integration ### Update Settings ```bash curl -X PUT https://api.lightningenable.com/api/merchant/shopify \ -H "X-API-Key: YOUR_LIGHTNING_ENABLE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "domesticShippingUsd": 6.99, "freeShippingEnabled": true, "freeShippingThresholdUsd": 75.00, "defaultTaxLocation": "US-TX-78701" }' ``` Only include fields you want to change — all others remain unchanged. To clear `defaultTaxLocation`, send an empty string (`""`). ### Refresh the Catalog Cache After updating products or prices in Shopify, force a cache refresh: ```bash curl -X POST https://api.lightningenable.com/api/merchant/shopify/invalidate-cache \ -H "X-API-Key: YOUR_LIGHTNING_ENABLE_API_KEY" ``` Or wait for the cache TTL to expire naturally (default: 15 minutes). ### View Orders ```bash # All orders curl "https://api.lightningenable.com/api/merchant/shopify/orders" \ -H "X-API-Key: YOUR_LIGHTNING_ENABLE_API_KEY" # Filter by status curl "https://api.lightningenable.com/api/merchant/shopify/orders?status=PaidWithDetails" \ -H "X-API-Key: YOUR_LIGHTNING_ENABLE_API_KEY" # Paginate curl "https://api.lightningenable.com/api/merchant/shopify/orders?page=2&pageSize=10" \ -H "X-API-Key: YOUR_LIGHTNING_ENABLE_API_KEY" ``` ### Pause the Integration To temporarily disable L402 purchases without losing your configuration: ```bash curl -X PUT https://api.lightningenable.com/api/merchant/shopify \ -H "X-API-Key: YOUR_LIGHTNING_ENABLE_API_KEY" \ -H "Content-Type: application/json" \ -d '{"isActive": false}' ``` Set `isActive` back to `true` to re-enable. ## Troubleshooting ### `Invalid API key or access token` from Shopify - Double-check that the **Client ID** and **Client Secret** pasted into Lightning Enable match what's shown in your Shopify app's configuration, with no extra whitespace. - Confirm the app is still **installed** on your store (Settings → Apps and sales channels → Develop apps → your app). Uninstalling the app revokes its credentials immediately. - If you regenerated the Client Secret, update it in your Lightning Enable dashboard — the previous secret stops working immediately. - If you updated the app's Admin API scopes after install, Shopify requires the app to be re-installed for the new scopes to take effect. Open the app in the Dev Dashboard and click **Install** (or **Update**) to re-apply. ### Catalog endpoint returns an empty list - Confirm your products are **Active**. Publishing to the **Online Store** sales channel is only required for Shopify's public storefront endpoints, not for the Admin API catalog fetch Lightning Enable uses. - Open `https://your-store.com/products.json` directly only if you want to verify public storefront visibility or suspect Lightning Enable is falling back to Shopify's public endpoint. If it returns an empty list, Shopify isn't serving the products publicly yet, but the Admin API catalog fetch can still work. - If you set `allowedProductTypes` on your integration, the values must match a product's Shopify `product_type` field, but letter casing does not matter. - The catalog is cached for 15 minutes by default — trigger a refresh by calling the `invalidate-cache` endpoint shown above in **Refresh the Catalog Cache**. ### Checkout returns a 400 about location Tax calculation needs to know where the buyer is. The agent must pass an `X-Buyer-Location` header on the checkout request (format `US-FL-34787`) — this header is **required** and there is no fallback. If it is missing or blank, checkout returns `400`. ### Order appears as unpaid or with no shipping info in Shopify admin - An order marked `PaidAwaitingDetails` in Lightning Enable means the Lightning payment succeeded but the buyer hasn't submitted the claim form yet. The default claim window is 30 days. - If the buyer never claims, the order does not post to your Shopify store at all — the agent paid, but no fulfillment request was ever made. Refunds for these cases are handled through your payment provider (e.g., Strike dashboard) as outgoing Lightning payments. ### Lightning invoice not being created at checkout - Verify your Strike API key is active in the Strike dashboard and has the `partner.receive-request.create` scope. - Re-save the key in your Lightning Enable dashboard to confirm it's stored correctly. ## Next Steps - [API Reference](/products/shopify-commerce/api-reference) — Full endpoint documentation with request/response schemas - [MCP Server Guide](/products/agentic-commerce/mcp-quickstart) — Help AI agents connect to your store - [Strike Setup](/strike-setup/account-setup) — Configure Strike as your payment provider ============================================================================== # checkout.js Source: https://docs.lightningenable.com/products/standalone/checkout-js ============================================================================== # checkout.js `checkout.js` (v2.1.0) is a small browser helper that connects your payment buttons to Lightning Enable's hosted checkout page. It handles the redirect UX — loading states, error callbacks, and sending the customer to the checkout URL your backend creates. :::info Security model - **Prices are never passed from the client** — that would let anyone edit the amount. - **Your API key never reaches the browser.** Your backend creates the payment with `X-API-Key`; the script only receives a checkout URL. - The script does not create invoices, render QR codes, or poll payment status — the hosted checkout page does that. ::: ## Architecture ``` Customer clicks button → checkout.js calls YOUR backend endpoint → your backend calls POST /api/payments (with your API key) → your backend returns { "checkoutUrl": "https://api.lightningenable.com/pay/" } → checkout.js redirects the customer to the hosted checkout page ``` Lightning Enable never holds funds — your payment provider (Strike or OpenNode) facilitates custody and settlement. ## Quick Start ### 1. Include the script ```html ``` ### 2. Create a checkout endpoint on your backend Your endpoint creates the payment and returns the checkout URL. `POST /api/payments` gives you that URL ready-made in `payUrl` — pass it straight through. :::caution The checkout URL is a secret The hosted checkout page lives at `https://api.lightningenable.com/pay/{paymentToken}`, where `paymentToken` is a random per-invoice value returned only in the create-payment response. It is what authorizes the page — anyone holding it can view that invoice's amount, description and Lightning invoice — so treat it like a payment link: send it to the buyer, don't log it, don't put it in a page a search engine can crawl. It cannot be derived from `invoiceId`. Lost it? Re-read `payUrl` from `GET /api/payments/{invoiceId}` with your API key. ::: ```javascript // Node/Express example app.post('/api/create-checkout', async (req, res) => { const response = await fetch('https://api.lightningenable.com/api/payments', { method: 'POST', headers: { 'X-API-Key': process.env.LIGHTNING_ENABLE_API_KEY, // server-side only 'Content-Type': 'application/json' }, body: JSON.stringify({ orderId: 'order-' + Date.now(), amount: 25.00, // price from YOUR catalog — never from the client currency: 'USD', description: 'Premium Widget' }) }); if (!response.ok) { return res.status(502).json({ error: 'Could not create checkout' }); } const payment = await response.json(); res.json({ checkoutUrl: payment.payUrl }); }); ``` ### 3. Add a payment button **Option A — dynamic session creation** (SPAs / dynamic sites). The button calls your endpoint, then redirects: ```html ``` **Option B — pre-created checkout URL** (static sites). Create the session ahead of time and link straight to it: ```html Pay with Lightning ``` Buttons are wired up automatically when the DOM is ready — no initialization call required. ## Configuration `LightningCheckout.init()` is optional. Call it only to customize error handling or the redirect: ```javascript LightningCheckout.init({ onError: function (error) { // error.message describes what failed (endpoint unreachable, missing checkoutUrl, ...) showToast('Payment failed to start: ' + error.message); }, onRedirect: function (url) { // Default is window.location.href = url. Override to open in a new tab, log, etc. window.open(url, '_blank'); } }); ``` | Option | Type | Default | Description | |--------|------|---------|-------------| | `onError` | `function(error)` | `console.error` | Called when session creation fails or a button is misconfigured | | `onRedirect` | `function(url)` | `window.location.href = url` | Called with the checkout URL to navigate to | ## Button attributes | Attribute | Applies to | Description | |-----------|-----------|-------------| | `data-checkout-endpoint` | Option A | Your backend endpoint that returns `{ "checkoutUrl": "..." }` | | `data-checkout-method` | Option A | HTTP method for the endpoint call (default `POST`) | | `data-checkout-url` | Option B | Pre-created checkout URL to redirect to | | any other `data-*` | Option A | Sent to your endpoint as the JSON request body — e.g. `data-product-id="42"` posts `{ "productId": "42" }` | While a session is being created the button is disabled and gets the `lightning-checkout-loading` CSS class (dimmed, `cursor: wait`) — style it further if you like. ## JavaScript API ```javascript LightningCheckout.init(options); // optional — configure callbacks LightningCheckout.redirectToCheckout(checkoutUrl); // programmatic redirect LightningCheckout.version; // "2.1.0" ``` Programmatic flow example: ```javascript async function buy(productId) { const res = await fetch('/api/create-checkout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ productId }) }); const { checkoutUrl } = await res.json(); LightningCheckout.redirectToCheckout(checkoutUrl); } ``` ## Confirming payment The hosted checkout page shows the invoice and updates when payment lands. To fulfill the order, use one of the server-side paths: - **Webhooks (recommended):** configure your callback URL and verify the `X-LightningEnable-Signature` header — see [Webhooks](/products/standalone/webhooks). - **Polling from your backend:** `GET /api/payments/{invoiceId}` with your API key. - **Browser-side UX only:** the public `GET /api/payments/{invoiceId}/status` endpoint returns `{ "status": "..." }` without an API key. ## Included in plans checkout.js and the hosted checkout page are available on both Agentic Commerce plans — Agentic Commerce ($49/mo) and Business ([contact us](mailto:support@lightningenable.com)). See [pricing](/products/product-overview). ## Next Steps - [Integration Guide](/products/standalone/integration) — the full REST API flow - [Webhooks](/products/standalone/webhooks) — payment notifications ============================================================================== # Integration Guide Source: https://docs.lightningenable.com/products/standalone/integration ============================================================================== # Integration Guide :::info Applies to Both Agentic Commerce Plans **"Standalone API" is a legacy plan name that is no longer sold.** The full REST API documented below is included in both current plans: - **[Agentic Commerce](/products/agentic-commerce/overview) ($49/mo)** — Full REST API + L402 protocol - **[Agentic Commerce — Business](/products/agentic-commerce/overview) ([contact us](mailto:support@lightningenable.com))** — Full REST API + pay-per-request API monetization + white-glove onboarding Everything in this guide applies to both plans (and to existing Standalone subscribers, who are unaffected). ::: This guide walks you through integrating the Lightning Enable REST API into your application. ## Overview Integration involves: 1. Creating payments when customers checkout 2. Displaying payment options (QR code, hosted checkout) 3. Receiving payment confirmations (webhooks or polling) 4. Fulfilling orders ## Authentication All API requests require an API key in the `X-API-Key` header: ```bash curl -X GET https://api.lightningenable.com/api/payments/inv_123 \ -H "X-API-Key: le_merchant_abc123..." ``` :::tip Store your API key in environment variables, never in code: ```bash export LIGHTNING_API_KEY="le_merchant_abc123..." ``` ::: ## Creating Payments ### Basic Payment ```javascript const response = await fetch('https://api.lightningenable.com/api/payments', { method: 'POST', headers: { 'X-API-Key': process.env.LIGHTNING_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ orderId: 'ORDER-12345', amount: 99.99, currency: 'USD', description: 'Annual Subscription' }) }); const payment = await response.json(); ``` ### Payment with Customer Details ```javascript const payment = await createPayment({ orderId: 'ORDER-12345', amount: 99.99, currency: 'USD', description: 'Annual Subscription', customerEmail: 'customer@example.com', customerName: 'John Doe', successUrl: 'https://yourapp.com/success', cancelUrl: 'https://yourapp.com/cancel', metadata: { customerId: 'cust_123', planId: 'pro_annual' } }); ``` ### Payment Request Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `orderId` | string | Yes | Your unique order identifier | | `amount` | decimal | Yes | Payment amount | | `currency` | string | Yes | Currency code (USD, EUR, GBP, BTC) | | `description` | string | No | Payment description | | `customerEmail` | string | No | Customer email | | `customerName` | string | No | Customer name | | `successUrl` | string | No | Redirect URL on success | | `cancelUrl` | string | No | Redirect URL on cancel | | `metadata` | object | No | Custom key-value data | ### Payment Response Fields | Field | Type | Description | |-------|------|-------------| | `invoiceId` | string | Lightning Enable invoice ID | | `openNodeChargeId` | string | OpenNode charge ID | | `status` | string | Payment status (unpaid, paid, expired) | | `amount` | decimal | Payment amount | | `currency` | string | Currency code | | `amountSats` | integer | Amount in satoshis | | `lightningInvoice` | string | BOLT11 Lightning invoice | | `onchainAddress` | string | Bitcoin address | | `hostedCheckoutUrl` | string | OpenNode checkout URL | | `createdAt` | datetime | Creation timestamp | | `expiresAt` | datetime | Expiration timestamp | ## Displaying Payment Options ### Option 1: Hosted Checkout (Easiest) Redirect customers to OpenNode's hosted checkout: ```javascript // After creating payment window.location.href = payment.hostedCheckoutUrl; ``` Pros: - No additional code needed - Mobile-optimized UI - Handles all payment methods Cons: - Customers leave your site ### Option 2: Embedded QR Code Display the Lightning invoice as a QR code: ```html ``` ### Option 3: Copy Invoice Let users copy the invoice to their wallet: ```html ``` ## Checking Payment Status ### Polling Query the status endpoint periodically: ```javascript async function checkStatus(invoiceId) { const response = await fetch( `https://api.lightningenable.com/api/payments/${invoiceId}`, { headers: { 'X-API-Key': process.env.LIGHTNING_API_KEY } } ); return await response.json(); } // Poll every 3 seconds const interval = setInterval(async () => { const payment = await checkStatus(invoiceId); if (payment.status === 'paid') { clearInterval(interval); fulfillOrder(payment.orderId); } else if (payment.status === 'expired') { clearInterval(interval); showExpiredMessage(); } }, 3000); ``` ### Webhooks (Recommended) Receive instant notifications when payment status changes: ```javascript // Express.js webhook handler — use express.raw() so the signature is // verified against the raw body, and verify BEFORE parsing app.post('/webhooks/lightning', express.raw({ type: 'application/json' }), (req, res) => { const signatureHeader = req.headers['x-lightningenable-signature']; // t={ts},v1={hmac} const payload = req.body.toString('utf8'); // HMAC-SHA256 over "{timestamp}.{payload}" — full implementation in the Webhooks docs if (!signatureHeader || !verifyWebhookSignature(payload, signatureHeader, process.env.WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); } const { invoiceId, orderId, status } = JSON.parse(payload); if (status === 'paid') { fulfillOrder(orderId); } res.status(200).send('OK'); }); ``` See [Webhooks Documentation](/products/standalone/webhooks) for complete implementation. ## Error Handling Handle common error scenarios: ```javascript async function createPayment(orderData) { try { const response = await fetch('https://api.lightningenable.com/api/payments', { method: 'POST', headers: { 'X-API-Key': process.env.LIGHTNING_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify(orderData) }); if (!response.ok) { const error = await response.json(); switch (response.status) { case 400: throw new Error(`Invalid request: ${error.message}`); case 401: throw new Error('Invalid API key'); case 429: throw new Error('Rate limit exceeded'); default: throw new Error(`API error: ${error.message}`); } } return await response.json(); } catch (error) { console.error('Payment creation failed:', error); throw error; } } ``` ## Language Examples ### C# / .NET ```csharp public class LightningEnableClient { private readonly HttpClient _client; private readonly string _apiKey; public LightningEnableClient(string apiKey) { _client = new HttpClient { BaseAddress = new Uri("https://api.lightningenable.com") }; _apiKey = apiKey; } public async Task CreatePaymentAsync(PaymentRequest request) { var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/payments") { Content = JsonContent.Create(request) }; httpRequest.Headers.Add("X-API-Key", _apiKey); var response = await _client.SendAsync(httpRequest); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync(); } } ``` ### Python ```python import requests import os class LightningEnableClient: def __init__(self, api_key=None): self.api_key = api_key or os.environ.get('LIGHTNING_API_KEY') self.base_url = 'https://api.lightningenable.com' def create_payment(self, order_id, amount, currency='USD', **kwargs): response = requests.post( f'{self.base_url}/api/payments', headers={ 'X-API-Key': self.api_key, 'Content-Type': 'application/json' }, json={ 'orderId': order_id, 'amount': amount, 'currency': currency, **kwargs } ) response.raise_for_status() return response.json() def get_payment(self, invoice_id): response = requests.get( f'{self.base_url}/api/payments/{invoice_id}', headers={'X-API-Key': self.api_key} ) response.raise_for_status() return response.json() ``` ### Go ```go package lightning import ( "bytes" "encoding/json" "net/http" ) type Client struct { APIKey string BaseURL string } func NewClient(apiKey string) *Client { return &Client{ APIKey: apiKey, BaseURL: "https://api.lightningenable.com", } } func (c *Client) CreatePayment(request PaymentRequest) (*PaymentResponse, error) { body, _ := json.Marshal(request) req, _ := http.NewRequest("POST", c.BaseURL+"/api/payments", bytes.NewBuffer(body)) req.Header.Set("X-API-Key", c.APIKey) req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() var payment PaymentResponse json.NewDecoder(resp.Body).Decode(&payment) return &payment, nil } ``` ## Best Practices ### Security - Store API keys in environment variables - Use HTTPS for all webhook endpoints - Verify webhook signatures - Validate all input data ### Reliability - Implement webhook retries on your end - Handle expired invoices gracefully - Store payment state in your database - Use idempotent order processing ### Performance - Don't poll more than once per 3 seconds - Cache exchange rates - Use webhooks instead of polling in production ## Next Steps - [Webhooks Setup](/products/standalone/webhooks) - Real-time notifications - [API Reference](/api-reference/overview) - Complete documentation ============================================================================== # Agentic Commerce Source: https://docs.lightningenable.com/products/standalone/overview ============================================================================== # Agentic Commerce The Lightning Enable **Agentic Commerce** plan (**$49/month**) provides full L402 access for developers building with Lightning payments and settlement via Strike API. ## What is the Agentic Commerce Plan? Agentic Commerce gives you everything you need to build and monetize APIs with Lightning payments: - **Unlimited L402 endpoints** - **Strike as settlement provider** - **Per-endpoint pricing** - **Live dashboard + per-request payment feed** ## Who Should Use This? The Agentic Commerce plan is ideal for: - **Individual developers** - Building L402 integrations and Lightning-powered APIs - **Side projects** - Monetizing APIs with micropayments - **AI builders** - Connecting agents to paid APIs - **Startups** - Testing payment models before scaling If you need white-glove onboarding and direct founder access, see [Agentic Commerce — Business](/products/agentic-commerce/overview) — [contact us](mailto:support@lightningenable.com). Not ready to subscribe at all? Start with the **[Free Producer Sandbox](/getting-started/activate-with-lightning)** — free, no card, 3 endpoints, 200 challenges/month, 1,000 sats max per challenge. ## Key Features ### API Middleware Architecture Lightning Enable never touches your funds - your payment provider (Strike or OpenNode) facilitates custody and settlement: ``` Your App --> Lightning Enable --> Strike/OpenNode --> Your Wallet ``` You bring your own API key, and all funds go directly to your provider account. ### L402 Protocol Monetize any API with pay-per-request Lightning payments. No user accounts needed — payment is the credential. ### Multi-Currency Support Accept payments in any currency your provider supports: - USD (default) - EUR - GBP - BTC (satoshis) ## Quick Example Create a payment with a single API call: ```bash curl -X POST https://api.lightningenable.com/api/payments \ -H "X-API-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "orderId": "ORDER-12345", "amount": 49.99, "currency": "USD", "description": "Premium Subscription", "successUrl": "https://yourapp.com/success" }' ``` ## Pricing **Agentic Commerce: $49/month** Includes: - Unlimited L402 endpoints - Strike as settlement provider - Per-endpoint pricing - Live dashboard + per-request payment feed Your payment provider may charge their own processing fees (paid directly to the provider, not to us). Teams that want white-glove onboarding and direct founder access should see [Agentic Commerce — Business](/products/agentic-commerce/overview) — [contact us](mailto:support@lightningenable.com) for details. ## Getting Started 1. [Sign up for Lightning Enable](https://lightningenable.com) 2. [Create a Strike account](/strike-setup/account-setup) (recommended) or [OpenNode account](/opennode-setup/account-setup) 3. [Configure your API keys](/getting-started/quick-start) 4. [Create your first L402 proxy](/products/agentic-commerce/api-monetization) ## Next Steps - [API Monetization](/products/agentic-commerce/api-monetization) - Monetize your API in 10 minutes - [MCP Quick Start](/products/agentic-commerce/mcp-quickstart) - AI agent integration - [API Reference](/api-reference/overview) - Complete endpoint documentation ============================================================================== # Webhooks Source: https://docs.lightningenable.com/products/standalone/webhooks ============================================================================== # Webhooks Webhooks provide instant notifications when payment status changes. Instead of polling the API, your server receives an HTTP POST request with payment details. ## Why Use Webhooks? | Method | Pros | Cons | |--------|------|------| | **Polling** | Simple to implement | Delayed detection, API overhead | | **Webhooks** | Instant, efficient | Requires public endpoint | For production use, **webhooks are strongly recommended**. ## Setup ### 1. Create a Webhook Endpoint Create an HTTP POST endpoint in your application: ```javascript // Express.js app.post('/webhooks/lightning', express.json(), async (req, res) => { try { // Process webhook console.log('Received webhook:', req.body); // Always return 200 quickly res.status(200).send('OK'); // Process asynchronously await processPayment(req.body); } catch (error) { console.error('Webhook error:', error); res.status(500).send('Error'); } }); ``` ### 2. Configure Webhook URL Set your webhook URL in Lightning Enable: **Option A: During merchant setup** ```json { "callbackUrl": "https://yourapp.com/webhooks/lightning" } ``` **Option B: Via merchant self-service API** ```bash curl -X PUT https://api.lightningenable.com/api/merchant/webhook-url \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{"webhookUrl": "https://yourapp.com/webhooks/lightning"}' ``` ### 3. Verify Webhook Signatures Always verify webhook signatures to ensure authenticity. Lightning Enable sends an `X-LightningEnable-Signature` header with every webhook, in the format `t={unix_timestamp},v1={hmac_sha256_hex}`. The HMAC is computed over `{timestamp}.{raw_body}` using your webhook secret. You should also reject signatures older than 5 minutes to prevent replay attacks. ```javascript const crypto = require('crypto'); const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; const TOLERANCE_SECONDS = 300; // 5 minutes function verifyWebhookSignature(payload, signatureHeader, secret) { // Parse "t={timestamp},v1={signature}" const parts = signatureHeader.split(','); let timestamp = null; let signature = null; for (const part of parts) { const trimmed = part.trim(); if (trimmed.startsWith('t=')) timestamp = parseInt(trimmed.slice(2), 10); else if (trimmed.startsWith('v1=')) signature = trimmed.slice(3); } if (!timestamp || !signature) return false; // Replay protection const now = Math.floor(Date.now() / 1000); if (Math.abs(now - timestamp) > TOLERANCE_SECONDS) return false; // Compute expected HMAC over "{timestamp}.{payload}" const signedPayload = `${timestamp}.${payload}`; const expected = crypto.createHmac('sha256', secret).update(signedPayload).digest('hex'); return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); } // Important: use express.raw() to get the raw body for signature verification app.post('/webhooks/lightning', express.raw({ type: 'application/json' }), (req, res) => { const signatureHeader = req.headers['x-lightningenable-signature']; const payload = req.body.toString('utf8'); if (!signatureHeader || !verifyWebhookSignature(payload, signatureHeader, WEBHOOK_SECRET)) { console.warn('Invalid webhook signature'); return res.status(401).send('Invalid signature'); } // Process verified webhook const event = JSON.parse(payload); processPayment(event); res.status(200).send('OK'); }); ``` ## Webhook Payload When a payment status changes, you receive a flat JSON object. The exact shape depends on your configured payment provider. **OpenNode merchants:** ```json { "invoiceId": "1042", "orderId": "ORDER-12345", "status": "paid", "amount": 25.00, "currency": "USD", "openNodeChargeId": "abc123-def456-...", "paidAt": "2026-07-03T12:03:45Z", "metadata": "{\"customerId\":\"cust_123\"}" } ``` **Strike merchants:** ```json { "invoiceId": "1042", "orderId": "ORDER-12345", "status": "paid", "amount": 25.00, "currency": "USD", "providerChargeId": "8f6c3f5e-1c2d-...", "provider": "strike", "paidAt": "2026-07-03T12:03:45Z", "metadata": null } ``` ### Payload Fields | Field | Type | Description | |-------|------|-------------| | `invoiceId` | string | Lightning Enable invoice ID (numeric string, e.g. `"1042"`) | | `orderId` | string | Your order ID | | `status` | string | Payment status — route on this field | | `amount` | decimal | Payment amount | | `currency` | string | Currency code | | `openNodeChargeId` / `providerChargeId` | string | The provider's charge ID (`provider: "strike"` accompanies the Strike form) | | `paidAt` | datetime | When Lightning Enable processed the event (UTC) | | `metadata` | string \| null | The metadata **JSON string** you supplied at payment creation — parse it before use | ### Payment Statuses | Status | Description | Action | |--------|-------------|--------| | `paid` | Payment confirmed | Fulfill order | | `processing` | Payment detected | Wait for confirmation | | `expired` | Invoice expired | Notify customer | | `underpaid` | Insufficient amount | Handle manually | | `refunded` | Payment refunded | Update records | ## Implementation Examples ### Node.js / Express ```javascript const express = require('express'); const crypto = require('crypto'); const app = express(); const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; const TOLERANCE_SECONDS = 300; // 5 minutes function verifySignature(payload, signatureHeader, secret) { const parts = signatureHeader.split(','); let timestamp = null; let signature = null; for (const part of parts) { const trimmed = part.trim(); if (trimmed.startsWith('t=')) timestamp = parseInt(trimmed.slice(2), 10); else if (trimmed.startsWith('v1=')) signature = trimmed.slice(3); } if (!timestamp || !signature) return false; // Replay protection const now = Math.floor(Date.now() / 1000); if (Math.abs(now - timestamp) > TOLERANCE_SECONDS) return false; // Compute HMAC over "{timestamp}.{payload}" const signedPayload = `${timestamp}.${payload}`; const expected = crypto.createHmac('sha256', secret).update(signedPayload).digest('hex'); return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); } // Use express.raw() to access the raw body for signature verification app.post('/webhooks/lightning', express.raw({ type: 'application/json' }), async (req, res) => { const signatureHeader = req.headers['x-lightningenable-signature']; const payload = req.body.toString('utf8'); if (!signatureHeader || !verifySignature(payload, signatureHeader, WEBHOOK_SECRET)) { return res.status(401).json({ error: 'Invalid signature' }); } const event = JSON.parse(payload); const { invoiceId, orderId, status } = event; switch (status) { case 'paid': { // Look up the order by your orderId — the payload carries no customer PII const order = await db.orders.findOne({ orderId }); await fulfillOrder(orderId); await sendConfirmationEmail(order.customerEmail); break; } case 'expired': await markOrderExpired(orderId); break; case 'refunded': await processRefund(orderId); break; } res.status(200).send('OK'); }); app.listen(3000); ``` ### C# / ASP.NET Core ```csharp using System.Security.Cryptography; using System.Text; using System.Text.Json; [ApiController] [Route("webhooks")] public class WebhooksController : ControllerBase { private readonly IOrderService _orderService; private readonly string _webhookSecret; private const int ToleranceSeconds = 300; // 5 minutes public WebhooksController(IOrderService orderService, IConfiguration config) { _orderService = orderService; _webhookSecret = config["WebhookSecret"]!; } [HttpPost("lightning")] public async Task HandleWebhook() { // Read the raw body for signature verification using var reader = new StreamReader(Request.Body); var payload = await reader.ReadToEndAsync(); var signatureHeader = Request.Headers["X-LightningEnable-Signature"].FirstOrDefault(); if (string.IsNullOrEmpty(signatureHeader) || !VerifySignature(payload, signatureHeader)) { return Unauthorized("Invalid signature"); } var webhookPayload = JsonSerializer.Deserialize(payload); switch (webhookPayload.Status) { case "paid": await _orderService.FulfillOrderAsync(webhookPayload.OrderId); break; case "expired": await _orderService.ExpireOrderAsync(webhookPayload.OrderId); break; } return Ok(); } private bool VerifySignature(string payload, string signatureHeader) { // Parse "t={timestamp},v1={signature}" long timestamp = 0; string providedSignature = ""; foreach (var part in signatureHeader.Split(',')) { var trimmed = part.Trim(); if (trimmed.StartsWith("t=") && long.TryParse(trimmed[2..], out var t)) timestamp = t; else if (trimmed.StartsWith("v1=")) providedSignature = trimmed[3..].ToLowerInvariant(); } if (timestamp == 0 || string.IsNullOrEmpty(providedSignature)) return false; // Replay protection var age = DateTimeOffset.UtcNow - DateTimeOffset.FromUnixTimeSeconds(timestamp); if (age.TotalSeconds > ToleranceSeconds || age.TotalSeconds < -30) return false; // Compute HMAC over "{timestamp}.{payload}" var signedPayload = $"{timestamp}.{payload}"; using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_webhookSecret)); var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(signedPayload)); var expected = Convert.ToHexString(hash).ToLowerInvariant(); // Constant-time comparison return CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(providedSignature)); } } public record WebhookPayload( string InvoiceId, string OrderId, string Status, decimal Amount, string Currency ); ``` ### Python / Flask ```python from flask import Flask, request, jsonify import hmac import hashlib import time import os app = Flask(__name__) WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET') TOLERANCE_SECONDS = 300 # 5 minutes def verify_signature(payload, signature_header, secret): """Verify X-LightningEnable-Signature with replay protection.""" timestamp = None signature = None for part in signature_header.split(','): trimmed = part.strip() if trimmed.startswith('t='): timestamp = int(trimmed[2:]) elif trimmed.startswith('v1='): signature = trimmed[3:] if timestamp is None or signature is None: return False # Replay protection now = int(time.time()) if abs(now - timestamp) > TOLERANCE_SECONDS: return False # Compute HMAC over "{timestamp}.{payload}" signed_payload = f'{timestamp}.{payload}' expected = hmac.new( secret.encode('utf-8'), signed_payload.encode('utf-8'), hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected) @app.route('/webhooks/lightning', methods=['POST']) def handle_webhook(): signature_header = request.headers.get('X-LightningEnable-Signature') payload = request.get_data(as_text=True) # Raw body, not parsed JSON if not signature_header or not verify_signature(payload, signature_header, WEBHOOK_SECRET): return jsonify({'error': 'Invalid signature'}), 401 data = request.json status = data.get('status') order_id = data.get('orderId') if status == 'paid': fulfill_order(order_id) elif status == 'expired': expire_order(order_id) return 'OK', 200 ``` ## Delivery & Reliability Treat webhooks as a fast-path notification, not a guaranteed delivery channel: - Each delivery attempt has a **10-second timeout**; the first attempt fires as soon as Lightning Enable processes the provider's webhook. A delivery is successful when your endpoint returns a `2xx` status code. - If your endpoint is down or errors, delivery is retried with exponential backoff — 30s, 60s, 120s, 240s, 480s (5 retry attempts, ~16-minute total window) — with **identical payload bytes** on every attempt (each attempt's `X-LightningEnable-Signature` is freshly timestamped and verifies against that same body — dedupe on payload content like `invoiceId` + `status`, never on the signature header) *(as of the July 2026 update; earlier versions did not retry failed forwards)*. - After retries exhaust, the event is marked permanently failed — recover by polling. **Recovery pattern:** reconcile any orders still pending on your side via `GET /api/payments/{invoiceId}` (authoritative), or force a provider re-check with `POST /api/payments/{invoiceId}/sync`. ## Local Development Use ngrok to test webhooks locally: ```bash # Install ngrok npm install -g ngrok # Start your local server npm start # Expose port 3000 ngrok http 3000 ``` Configure the ngrok URL as your webhook endpoint: ``` https://abc123.ngrok.io/webhooks/lightning ``` ## Best Practices ### Return 200 Quickly Process webhooks asynchronously to avoid timeouts: ```javascript app.post('/webhooks/lightning', (req, res) => { // Return 200 immediately res.status(200).send('OK'); // Process asynchronously processWebhookAsync(req.body).catch(console.error); }); ``` ### Idempotent Processing Handle duplicate webhooks gracefully: ```javascript async function processPayment(payload) { // Check if already processed const existing = await db.payments.findOne({ invoiceId: payload.invoiceId }); if (existing && existing.processed) { console.log('Already processed:', payload.invoiceId); return; } // Process payment await fulfillOrder(payload.orderId); // Mark as processed await db.payments.updateOne( { invoiceId: payload.invoiceId }, { $set: { processed: true, processedAt: new Date() } } ); } ``` ### Log Everything Keep detailed logs for debugging: ```javascript app.post('/webhooks/lightning', express.raw({ type: 'application/json' }), (req, res) => { const payload = req.body.toString('utf8'); const event = JSON.parse(payload); console.log('Webhook received:', { invoiceId: event.invoiceId, status: event.status, signature: req.headers['x-lightningenable-signature']?.substring(0, 20) + '...' }); // ... process webhook }); ``` ## Troubleshooting ### Webhooks Not Received 1. Verify webhook URL is publicly accessible 2. Check for HTTPS requirement 3. Ensure firewall allows incoming connections 4. Review webhook logs in Lightning Enable dashboard ### Signature Verification Fails 1. Ensure you're using the correct webhook secret 2. Verify you're using the **raw request body**, not re-serialized JSON 3. Check the header name is `X-LightningEnable-Signature` (not `X-Webhook-Signature`) 4. Ensure you're computing the HMAC over `{timestamp}.{payload}`, not just the payload 5. Check that your replay protection tolerance is at least 5 minutes 6. Check for encoding issues (UTF-8) ### Duplicate Webhooks 1. Implement idempotent processing 2. Store invoice IDs that have been processed 3. Check before fulfilling orders ## Next Steps - [Error Handling](/api-reference/errors) - Error codes and handling - [API Reference](/api-reference/webhooks) - Complete webhook documentation ============================================================================== # Subscription & Plan Enforcement Source: https://docs.lightningenable.com/products/subscription-management ============================================================================== # Subscription & Plan Enforcement Lightning Enable enforces active subscriptions and plan-specific feature access on every API request. This page explains how plan tiers work, what happens when a subscription lapses, and how feature gating controls access to plan-specific functionality. ## Plan Tiers Lightning Enable has three plan tiers. Pricing is based on capabilities, never transaction volume. Platform integrations (Shopify Commerce) are included with any paid plan. | Plan | Tier ID | Price | Stripe subscription | |------|---------|-------|---------------------| | **Free Producer Sandbox** | `free` | $0 | Not required | | **Agentic Commerce** | `individual` | $49/month | Required | | **Agentic Commerce — Business** | `l402` | Contact us | Required | Agentic Commerce includes a 30-day free trial through self-serve checkout; neither paid plan offers annual billing. Free is the floor: an account with no plan tier set resolves to Free, so there is no paid default. Agentic Commerce — Business is **contact-only**: it is not purchasable through `/Checkout`, `/BitcoinCheckout`, `POST /api/stripe/create-checkout-session`, or `POST /api/bitcoin/create-subscription` — a request naming it on any of those surfaces is rejected with a `400`. To subscribe, email support@lightningenable.com. Existing Business subscribers keep everything shown in this page's tables (feature entitlements, limits, renewal) unaffected. ### Retired tier ids As of September 2026, three tiers exist. The `pilot`, `standalone` (also spelled `standaloneapi`), and `standard` (Kentico Commerce) tiers were removed after a production snapshot confirmed no account was on any of them. Their spellings still **resolve**, so an account created before the change and an old checkout link both keep working: | Retired id | Resolves to | |------------|-------------| | `standard`, `kenticocommerce`, `kentico-commerce`, `kentico` | `individual` | | `standalone`, `standaloneapi`, `standalone-api` | `individual` | | `pilot` | `free` | Retired paid ids resolve to Individual rather than Free so an existing subscriber is never silently downgraded. Resolving is not the same as selecting. You cannot start anything new on a retired tier — `POST /api/stripe/create-checkout-session` and `POST /api/bitcoin/create-subscription` reject a retired id with a `400`. The admin endpoint `PUT /api/admin/merchants/{id}` still accepts one and rewrites it to the live tier it maps to; a tier id that is neither live nor retired is rejected with a `400` that names the accepted values. ### Feature Comparison Every plan includes core API access. Higher tiers unlock additional capabilities. | Feature | Free Producer Sandbox | Agentic Commerce | Agentic Commerce — Business | |---------|------|-----------|------------------------| | Full REST API | Yes | Yes | Yes | | Lightning Network payments | Yes | Yes | Yes | | Multi-currency (USD, EUR, GBP, BTC) | No | Yes | Yes | | Analytics | No | Yes | Yes | | Priority support | No | Yes | Yes | | Max environments | 1 | 2 | 2 | | Max webhook endpoints | 1 | 5 | 5 | | Platform integrations (Shopify) | No | Yes | Yes | | L402 protocol (server-side) | **Capped** | **Yes** | **Yes** | | Pay-per-request monetization | **Capped** | **Yes** | **Yes** | | MCP AI agent integration | **Yes** | **Yes** | **Yes** | | White-glove onboarding | No | No | **Yes** | | Custom branding | No | No | No | Free is capped rather than unlimited: 3 L402 endpoints, 200 challenges per month, 1,000 sats maximum per challenge, and 1 proxy configuration. The endpoint cap counts distinct resource paths for the life of the account; the challenge cap resets monthly. ### Checking Your Plan Use the merchant settings endpoint to see your current plan and features: ```bash curl https://api.lightningenable.com/api/merchant/me \ -H "X-API-Key: YOUR_API_KEY" ``` The response includes your `planTier`, `subscriptionStatus`, and a `features` object with all feature flags. --- ## Free Trial Lightning Enable offers a **30-day free trial** on self-serve Agentic Commerce checkout, giving you full API access before your first payment. ### How It Works - **Eligible plan:** Agentic Commerce ($49/mo), via self-serve checkout - **Duration:** 30 days from the date of subscription - **Card required:** A valid payment method must be provided at signup. You will not be charged during the trial period. - **Full access:** Trial merchants have complete API access, identical to a paid subscription. The `subscriptionStatus` will show `trialing`. - **Auto-converts:** At the end of the 30-day trial, the subscription automatically converts to a paid plan. Your card on file will be charged at the plan's regular rate. - **Cancel anytime:** You can cancel before the trial ends to avoid being charged. Use the Stripe customer portal to manage your subscription. Agentic Commerce — Business is contact-only and does not go through this self-serve trial flow. Email support@lightningenable.com and any trial terms will be arranged directly. ### Abuse Prevention To maintain fair access, Lightning Enable enforces **one free trial per email address**. If a customer has previously used a trial (on any plan), subsequent subscriptions will skip the trial period and begin billing immediately. ### Trial Eligibility by Plan | Plan | Trial Eligible | |------|---------------| | Free Producer Sandbox ($0) | No -- Free is the floor, not a trial | | Agentic Commerce ($49/mo) | Yes, via self-serve checkout | | Agentic Commerce -- Business (contact us) | Arranged directly on contact, not via self-serve checkout | --- ## Subscription Lifecycle ### Valid Subscription States The subscription enforcement middleware checks every authenticated API request. Only two statuses grant access: | Status | Meaning | API Access | |--------|---------|------------| | `active` | Subscription is current and paid | Allowed | | `trialing` | In free trial period | Allowed | | `past_due` | Payment failed, awaiting retry | **Blocked** | | `canceled` | Subscription was canceled | **Blocked** | | `unpaid` | Payment not received | **Blocked** | | `incomplete` | Initial payment not completed | **Blocked** | | `incomplete_expired` | Initial payment window expired | **Blocked** | ### Subscription Validation Flow The middleware performs these checks in order for every authenticated request: 1. **Path exemption** -- Certain paths skip subscription checks entirely (Stripe endpoints, webhooks, health checks, Swagger). 2. **Account active check** -- If the merchant account is deactivated (`isActive = false`), the request is immediately blocked with a `403`. 3. **Free carve-out** -- An account whose tier resolves to `free` skips the subscription checks entirely, because Free requires no Stripe subscription. Capacity is enforced elsewhere, by the Free caps on the L402 and proxy endpoints. Two guards narrow this carve-out (see [Free carve-out guards](#free-carve-out-guards) below). 4. **L402 fast-lane trial carve-out** -- An account created by the L402 Fast Lane also skips the subscription checks for the length of its trial (see [L402 fast-lane trial](#l402-fast-lane-trial) below). 5. **Stripe subscription required** -- Every account that took no carve-out must have a valid `StripeSubscriptionId`. Accounts without one receive a `403` with `action_required: "subscribe"`. 6. **Subscription status check** -- The status must be `active` or `trialing`. Any other status returns a `403` with a status-specific message. 7. **Billing period validation** -- If `CurrentPeriodEnd` is set and has passed, the request is blocked even if the status field still shows `active`. This catches expired subscriptions before the Stripe webhook updates the status. 8. **Feature gating** -- Plan-specific features are checked against the requested endpoint (see [Feature Gating](#feature-gating) below). This step runs on **every** path that reached it, carve-outs included, so a Free account cannot reach a paid-only endpoint just because the subscription check was skipped. ``` Request │ ├─ Exempt path? ──── Yes ──→ Allow │ ├─ No MerchantId? ── Yes ──→ Allow (unauthenticated) │ ├─ Account inactive? ────── → 403 "Account inactive" │ ├─ Tier resolves to free │ (and may take a carve-out)? ─ Yes ──→ Feature gate ──→ Allow │ ├─ L402 fast-lane trial, │ still within CurrentPeriodEnd? ─ Yes ─→ Feature gate ──→ Allow │ ├─ No Stripe sub? ─────────→ 403 "Subscription required" │ ├─ Status not active/trialing? → 403 "Subscription not active" │ ├─ CurrentPeriodEnd passed? ──→ 403 "Subscription period expired" │ ├─ Feature not available? ────→ 403 "Feature not available" │ └─ All checks pass ─────────→ Allow (features set in context) ``` ### Free carve-out guards The Free carve-out grants API access with no Stripe subscription, so two states are deliberately excluded from it: - **An unrecognized tier id.** A tier value that is neither live nor retired — an operator typo, say — does **not** take the carve-out. It falls through to the subscription checks, which fail closed. Reading an unrecognized value as Free would hand it permanent unpaid access. - **A blank tier that carries Stripe billing state.** A blank tier normally means "never set", and Free is the right reading. A blank tier on a row that also holds a `stripeCustomerId` or `stripeSubscriptionId` is a data anomaly, not a Free account, so it faces the subscription checks. Otherwise a canceled subscription would regain full access by way of the carve-out. A blank tier with no billing state is the ordinary case and is admitted as Free. ### L402 fast-lane trial An account created through the [L402 Fast Lane](/getting-started/activate-with-lightning) has no Stripe subscription — the 100-sat Lightning payment is the proof of intent — so it needs a carve-out of its own. It applies only when all four of these hold: - the stored tier is exactly `individual` - `subscriptionStatus` is `trialing` - both `stripeSubscriptionId` and `stripeCustomerId` are empty - `currentPeriodEnd` is in the future The first condition matches the **stored** value, not the resolved one. That is deliberate and narrower than it looks: an account stored under a retired id resolves to `individual` everywhere else but does **not** take this carve-out, because the background job that ends the trial filters on the stored string in SQL and could not find it either. Matching wider here than the job can find would turn a bounded trial into permanent unpaid access. The trial ends by way of a background job that downgrades the account to Free once `trialEnd` passes with no Stripe conversion. That job additionally requires the account to be marked as fast-lane-originated and not to have billing deferred, so an account with deferred billing stays on Individual rather than being downgraded. Adding billing mid-trial bills immediately and does not start a second trial. --- ## Subscription Expiration ### What Happens When a Subscription Expires When a subscription expires or is canceled, API requests return `403 Forbidden` with a JSON body describing the issue and what action to take. **Canceled subscription:** ```json { "error": "Subscription not active", "message": "Your subscription has been canceled. Please subscribe again to continue using the service.", "subscription_status": "canceled", "action_required": "renew_subscription" } ``` **Past due payment:** ```json { "error": "Subscription not active", "message": "Your subscription payment is past due. Please update your payment method to continue using the service.", "subscription_status": "past_due", "action_required": "update_payment_method" } ``` **Billing period expired (webhook delay protection):** ```json { "error": "Subscription period expired", "message": "Your subscription billing period has expired. Please renew your subscription to continue using the service.", "subscription_status": "active", "current_period_end": "2026-01-15T00:00:00.0000000Z", "action_required": "renew_subscription" } ``` :::warning CurrentPeriodEnd Validation Even if the subscription status still reads `active`, the middleware checks whether `CurrentPeriodEnd` has passed. This provides a safety net for cases where Stripe webhook delivery is delayed, ensuring expired subscriptions are caught in near-real-time. ::: ### Grace Period Behavior Lightning Enable relies on Stripe's built-in retry and grace period logic: - **Stripe retries** failed payments automatically according to your Stripe account's [Smart Retries](https://stripe.com/docs/billing/revenue-recovery/smart-retries) settings (typically 3-4 attempts over several days). - **During retries**, the subscription status transitions to `past_due`. API access is **blocked** during this period. - **If all retries fail**, Stripe marks the subscription as `canceled` or `unpaid` depending on your Stripe settings. - **There is no additional grace period** built into Lightning Enable beyond what Stripe provides. The moment the subscription status leaves `active` or `trialing`, API access is blocked. To restore access after a lapsed subscription: 1. Update your payment method via the Stripe customer portal. 2. Or subscribe again at [lightningenable.com](https://lightningenable.com). ### Exempt Paths These paths are never subject to subscription enforcement, even for expired accounts: | Path | Reason | |------|--------| | `/api/stripe/create-checkout-session` | Must be accessible to (re)subscribe | | `/api/stripe/customer-portal` | Must be accessible to manage billing | | `/api/stripe/subscription` | Must be accessible to check status | | `/api/stripe/pricing` | Public pricing information | | `/api/webhooks/stripe` | Incoming Stripe webhooks | | `/api/webhooks/opennode` | Incoming OpenNode webhooks | | `/api/webhooks/strike` | Incoming Strike webhooks | | `/api/l402/pricing` | L402 demo pricing (uses L402 token auth) | | `/api/l402/status` | L402 demo status (uses L402 token auth) | | `/api/l402/demo` | L402 demo endpoint (uses L402 token auth) | | `/api/l402/premium-data` | L402 demo premium data (uses L402 token auth) | | `/api/l402/content` | L402-protected premium guides (uses L402 token auth) | | `/api/manifests` | Public manifest registry | | `/l402/test` | L402 public test endpoint (1-sat ping) | | `/health` | Health check | | `/swagger` | API documentation | Only the specific L402 demo paths listed above are exempt. The L402 producer API (`/api/l402/challenges` and `/api/l402/challenges/verify`) **is** subscription-enforced and feature-gated — it requires an active Agentic Commerce subscription with `L402Enabled`. This ensures merchants can always manage their subscription and billing even when their API access is blocked. --- ## Evaluating without a subscription Two paths give you API access with no Stripe subscription. Both are self-serve; neither needs an administrator. | Path | Tier | Duration | Limits | |------|------|----------|--------| | [Free Producer Sandbox](https://api.lightningenable.com/dashboard/signup) | `free` | Indefinite | 3 endpoints, 200 challenges/month, 1,000 sats max per challenge, 1 proxy | | [L402 Fast Lane](/getting-started/activate-with-lightning) | `individual` | 30 days | None — a full Individual trial | The admin-created `pilot` tier that used to serve this purpose was removed in September 2026. An account still stored as `pilot` resolves to `free` and takes the Free carve-out, so it keeps working at Free capacity. --- ## Feature Gating Beyond subscription status, the middleware enforces plan-specific feature access on certain endpoints. ### Gated Features Each gate reads a **per-account flag**, not the plan table. Plan changes set those flags, but an operator can also set them individually. | Feature | Gated Endpoint | Account flag | `required_plan` | `action_required` | |---------|----------------|--------------|-----------------|-------------------| | `refunds` | `/api/refunds/*` | `refundsEnabled` | `null` | `contact_support` | | `multi_currency` | `/api/payments/*/convert` | `multiCurrencyEnabled` | `individual` | `upgrade_plan` | | `l402` | `/api/l402/challenges*` | `l402Enabled` | `individual` | `upgrade_plan` | If a merchant attempts to access a gated endpoint without the required feature flag, they receive: ```json { "error": "Feature not available", "message": "Refund processing is not enabled for your account. Please contact support.", "feature": "refunds", "current_plan": "free", "required_plan": null, "action_required": "contact_support" } ``` :::note Refunds are never granted by a plan No plan sets `refundsEnabled` — it is an operator-granted per-account flag. The refunds `403` therefore sends `required_plan: null` and `action_required: "contact_support"`, because upgrading would not turn the feature on. Contact support@lightningenable.com instead. ::: :::note Key on `feature`, not on the tier name Key your error handling on the `feature` field. `current_plan` is the **normalized** tier: it is always one of `free`, `individual`, or `l402` for an account whose tier we recognize, even when the stored value is a retired spelling. There are two exceptions: an unrecognized tier value is echoed back raw so an operator can see what needs fixing, and a blank tier (no plan on file) is reported as `null`. ::: ### Feature Flags in Context When a request passes all subscription and feature checks, the middleware populates `MerchantFeatures` in the request context. Controllers can use these flags for fine-grained access control: | Feature Flag | Type | Description | |-------------|------|-------------| | `RefundsEnabled` | boolean | Can process refunds | | `MultiCurrencyEnabled` | boolean | Can use multi-currency conversion | | `MaxWebhookEndpoints` | int | Maximum webhook endpoints allowed | | `AnalyticsEnabled` | boolean | Access to analytics | | `PrioritySupport` | boolean | Priority support access | | `CustomBrandingEnabled` | boolean | Custom branding on checkout | ### L402 Feature Gating L402 server-side features (creating proxies, configuring endpoint pricing, the producer API) are available on **every** live tier. Access is controlled by the `l402Enabled` flag on the merchant account, which the plan sets to `true` on all three. What differs is capacity, not availability: Free is capped, both paid plans are not. Check your L402 status: ```bash curl https://api.lightningenable.com/api/merchant/l402-status \ -H "X-API-Key: YOUR_API_KEY" ``` | Plan | L402 Server-Side | Price | |------|------------------|-------| | Free Producer Sandbox | **Yes** — capped at 3 endpoints, 200 challenges/mo, 1,000 sats per challenge | $0 | | Agentic Commerce | **Yes** — uncapped | $49/mo | | Agentic Commerce — Business | **Yes** — uncapped | Contact us | :::tip MCP Tools Are Free The MCP server's L402 *client* tools (`access_l402_resource`, `pay_l402_challenge`) are free for everyone. No subscription is needed to *pay* L402 invoices -- only to *create* L402-protected endpoints. ::: --- ## Handling Subscription Errors in Your Integration ### Detecting Subscription Issues All subscription-related errors return HTTP `403` with an `action_required` field. Use this field to determine the appropriate response: | `action_required` | Meaning | Recommended Action | |-------------------|---------|-------------------| | `contact_support` | Account deactivated, **or** a feature that no plan grants (refunds) | Contact support@lightningenable.com. Do not offer an upgrade | | `subscribe` | No active subscription | Redirect to subscription page | | `update_payment_method` | Payment failed | Redirect to Stripe customer portal | | `renew_subscription` | Subscription expired or canceled | Redirect to subscription page | | `upgrade_plan` | Feature requires a different plan | Show upgrade options for `required_plan` | Branch on `action_required`, not on `error`. `contact_support` covers two different causes, and `error` tells you which: `"Account inactive"` versus `"Feature not available"`. ### Example Error Handler ```javascript async function callLightningEnableApi(endpoint) { const response = await fetch(`https://api.lightningenable.com${endpoint}`, { headers: { 'X-API-Key': process.env.LIGHTNING_API_KEY } }); if (response.status === 403) { const error = await response.json(); switch (error.action_required) { case 'subscribe': case 'renew_subscription': console.error('Subscription issue:', error.message); // Redirect user to subscription page break; case 'update_payment_method': console.error('Payment issue:', error.message); // Redirect user to Stripe customer portal break; case 'upgrade_plan': // required_plan is null when no plan grants the feature, so guard it. if (error.required_plan) { console.error(`Feature "${error.feature}" requires the ${error.required_plan} plan`); // Show upgrade options } else { console.error(`Feature "${error.feature}" is not available on any plan:`, error.message); // Point the user at support, not at checkout } break; case 'contact_support': // Two causes: a deactivated account, or a feature no plan grants // (refunds). error.feature is present only for the second. if (error.feature) { console.error(`Feature "${error.feature}" must be enabled by support:`, error.message); } else { console.error('Account issue:', error.message); } break; } throw new Error(error.message); } return response.json(); } ``` --- ## Next Steps - [Product Overview](/products/product-overview) -- Compare plan features - [Error Code Reference](/api-reference/errors) -- All API error codes - [Merchant Settings](/api-reference/merchant-settings) -- Check subscription status via API - [FAQ](/faq) -- Common questions about plans and pricing ============================================================================== # Release Notes Source: https://docs.lightningenable.com/release-notes ============================================================================== # Release Notes ## API ### Repricing (September 2026) The publicly marketed paid plan (tier id `individual`) moved from **$99/month to $49/month** and lost the "— Individual" suffix from its display name — it now shows simply as **Agentic Commerce**. Annual billing was dropped: no plan advertises an annual price. - **Agentic Commerce — Business** (tier id `l402`, previously $299/month) went **contact-only** (owner decision, 2026-09-02): it is de-listed from public pricing pages and from the pricing feeds — `GET /api/stripe/pricing` and `GET /api/bitcoin/pricing` now return only the publicly listed plans, so Business no longer appears in either response. `POST /api/stripe/create-checkout-session`, `POST /api/bitcoin/create-subscription`, `/Checkout`, and `/BitcoinCheckout` all reject a new Business subscription attempt with a `400` pointing to support@lightningenable.com. Existing Business subscribers, their renewals, and the admin endpoint (`PUT /api/admin/merchants/{id}`) are unaffected - **Free Producer Sandbox** (tier id `free`, $0, no card) is promoted as the default way to start: 3 endpoints, 200 challenges/month, 1,000 sats maximum per challenge - Tier ids and `Merchant.PlanTier` resolution are unchanged — existing accounts on any tier keep working exactly as before. Shipped as a code PR (lightning-enable#412) plus a documentation-only follow-up (lightning-enable#415) across the docs site, README, CLAUDE.md, and TERMS-OF-SERVICE.md ### Plan Tier Consolidation (September 2026) Six plan tiers collapsed to three: **`free`** (Free Producer Sandbox), **`individual`** (Agentic Commerce — Individual), and **`l402`** (Agentic Commerce — Business). The `pilot`, `standalone`, and `standard` (Kentico Commerce) tiers were removed after a production snapshot confirmed no account was on any of them. Prices are unchanged. - **Retired tier ids still resolve, but cannot be selected** — an account or an old link carrying `standard`, `kenticocommerce`, `standalone`, or `standaloneapi` resolves to `individual`, and `pilot` resolves to `free`, so nothing that worked stops working. Retired paid ids resolve to Individual rather than Free so an existing subscriber is never silently downgraded - **`planTier` is now the normalized value** on `GET /api/merchant/me`, `GET /api/merchant/l402-status`, and `GET /api/merchant/subscription` — a **contract change**. These three fields previously returned the raw stored column, so the same unset account was reported as `pilot` by two of them and `standalone` by the third. They now always return one of the three live tier ids. Update any client that string-matches a retired id - **`GET /api/stripe/pricing` returns both paid plans** — it previously returned exactly one, the retired Kentico tier that could not be purchased - **Checkout endpoints accept the canonical `l402`** — `POST /api/stripe/create-checkout-session` and `POST /api/bitcoin/create-subscription` take `individual`, `l402`, or `l402microtransactions`, and reject a retired spelling with a `400` instead of failing later in the service - **The refunds `403` no longer names a plan** — it sends `required_plan: null` and `action_required: "contact_support"`. No plan grants refunds; `refundsEnabled` is a per-account flag an operator sets on request, so the previous `upgrade_plan` sent customers to buy an upgrade that would not have helped - **`PUT /api/admin/merchants/{id}` rejects an unrecognized or blank `planTier`** with a `400` naming the accepted values, rather than persisting a typo. A retired spelling is still accepted and rewritten to the live tier - **Display fixes** — the `/Success` page after a Stripe checkout shows "Agentic Commerce — Business" instead of the raw `l402microtransactions`; welcome and payment emails no longer quote the retired Kentico plan or fall back to "Free" at $0.00 after a payment; `/Checkout?plan=standalone` preselects Individual instead of Business Shipped across four stacked pull requests: #406 (core), #408 (write paths), #409 (display surfaces), #410 (documentation). --- ### Documentation Accuracy Overhaul (July 2026) Full reconciliation audit of the docs site against the shipped product, fixing pages that had drifted from the code. - Corrected the documented **webhook**, **refund**, and **payment** API contracts to match actual response shapes and endpoints - Removed endpoints and features that never shipped (including a phantom Exchange Rates API) and fixed plan-matrix contradictions - Removed internal runbook content and any tutorial patterns that put API keys in browser-side code - Backfilled these release notes — the API section had not been updated since March 2026 --- ### L402 Protocol Fixes (June 2026) - **Caveat intersection** — repeated macaroon caveats now intersect (monotonic narrowing) instead of last-write-wins, closing a scope-widening hole - **Proxy macaroon scoping** — proxy macaroon verification is bound to the merchant and path it was issued for - **Spec-compliant identifier encoding** — the macaroon identifier is encoded as raw bytes per the L402 spec, improving interoperability with other L402 tooling --- ### Security Hardening (May–June 2026) Fixes from the May 2026 security audit, plus CI scanning. - **Per-IP auth-failure throttle** — repeated failed authentication attempts from one IP return 429 with a `Retry-After` header (defaults: 20 failures per 60-second window); also covers the Hangfire dashboard - **Constant-time comparisons** on all secret checks (API keys, admin key, webhook signatures) - **Public checkout status endpoints** trimmed to return only `{ status }` — enumeration-safe, no side effects - **Antiforgery enforcement** on dashboard login and magic-link forms - **HSTS header** (staged, config-driven rollout; June 2026) - CodeQL, Semgrep, and gitleaks security scanning added to CI --- ### Native L402 Server SDKs (May 2026) Native mode launched: monetize your API on your own domain — traffic never flows through Lightning Enable. See the [Native Integration guide](/products/agentic-commerce/native-integration). - **Node.js:** [`l402-server`](https://www.npmjs.com/package/l402-server) (SDK) and [`l402-express`](https://www.npmjs.com/package/l402-express) (Express middleware) - **.NET:** [`L402Server`](https://www.nuget.org/packages/L402Server) (SDK) and [`L402Server.AspNetCore`](https://www.nuget.org/packages/L402Server.AspNetCore) (ASP.NET Core middleware) - Two live, open-source reference apps: [l402-example-node](https://github.com/refined-element/l402-example-node) and [l402-example-aspnet](https://github.com/refined-element/l402-example-aspnet) - New docs: [Express walkthrough](/products/agentic-commerce/native-integration-express), [ASP.NET Core walkthrough](/products/agentic-commerce/native-integration-aspnet), and [Producer API Reference](/products/agentic-commerce/producer-api-reference) --- ### Dashboard: Smart Setup & Test-It Panel (May 2026) - **Smart Setup** — paste an OpenAPI spec URL and the dashboard auto-detects your endpoints to create an L402 proxy - **Test-It panel** — live 402 preview per endpoint, so you can see exactly what agents receive before going live - Merged Pricing tab and self-service **Regenerate API key** button on Settings --- ### Merchant CORS Auto-Allowlist & Canonical Shopify Identity (May 2026) - **Merchant-origin CORS auto-allowlist** — merchant storefront domains are allowed automatically via the `MerchantOrigins` table; no manual CORS configuration per merchant - **Canonical Shopify shop identity** — the `*.myshopify.com` handle and primary domain are read from Shopify's own `shop.json` (never derived heuristically) on OAuth install, credential save, and an explicit dashboard refresh button; admin backfill and scope-probe diagnostics heal legacy rows --- ### Premium Guides & L402 Tool APIs (May 2026) - **Premium guide catalog expanded** and repriced; guides are L402-protected content endpoints — pay per guide with any L402 client (first shipped March 2026) - **Tier 1 L402 tool APIs** added to the registry --- ### Shopify: Draft-Order Tax & Required Buyer Location (April 2026) - **Tax via Shopify Draft Orders** — checkout creates a temporary draft order to get Shopify-calculated tax, which is included in the Lightning invoice total (requires the `write_draft_orders` scope) - **`X-Buyer-Location` header is now required** at Shopify checkout (`{country}-{state}-{zip}`, the buyer's location); the stored default-location fallback was removed - **30-day free trial** on Agentic Commerce plans (card required, no charge until the trial ends) --- ### v1.3.0 — Public L402 Test Endpoint (March 2026) - **Public test endpoint** — `GET https://api.lightningenable.com/l402/test/ping` returns a 402 with a 1-sat invoice, no API key or signup required - Pay the invoice, retry with `Authorization: L402 :`, get a 200 — full L402 round-trip in two curl commands - Alby Hub officially verified as L402-compatible --- ### Shopify L402 Integration (March 2026) AI agents can browse a Shopify catalog, pay via Lightning, and create real Shopify orders — L402 cryptographic proof is the payment verification, no webhook dependency. - Public store endpoints: catalog → checkout (402 + Lightning invoice) → claim with `Authorization: L402 {macaroon}:{preimage}` + shipping address - **Shopify OAuth app install** from the dashboard, with manual token entry preserved as an advanced fallback - Claim tokens allow a configurable window (default 30 days) to provide shipping details after payment; expired unclaimed orders are auto-purged - Active Shopify stores are auto-registered in the L402 API registry for agent discovery - See the [Shopify Commerce docs](/products/shopify-commerce/overview) --- ### v1.2.0 — L402 Producer API (March 2026) Agents can now **earn**, not just spend. The L402 Producer API enables Agentic Commerce subscribers to create L402 payment challenges and verify payments — powering agent-to-agent commerce where AI agents autonomously buy and sell services. #### New Features - **L402 Producer API** — Two new endpoints (`POST /api/l402/challenges`, `POST /api/l402/challenges/verify`) let merchants programmatically create L402 payment challenges and verify L402 tokens - **Challenge idempotency** — Same resource + price from the same client within 60 seconds returns the same invoice, preventing duplicate charges on retries - **Two new MCP tools** — `create_l402_challenge` and `verify_l402_payment` bring producer capabilities directly to AI agents via MCP #### Documentation - New [L402 Producer API guide](/products/agentic-commerce/l402-producer-api) with end-to-end agent-to-agent commerce examples - Updated [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) with producer tool reference - Updated [L402 API Reference](/api-reference/l402) with producer endpoints --- ### Strike Payment Provider (February 2026) Multi-provider settlement abstraction: merchants choose Strike or OpenNode as their payment provider. Lightning Enable does not hold funds — the configured provider facilitates custody and settlement. - **Strike is the default provider** on the hosted service (`PaymentProvider:Default`); each merchant can override per-account - Strike returns the **preimage** on outgoing payments, which L402 requires — making it the recommended provider for L402 workloads - Strike webhooks supported (thin payloads — the API fetches full entity details after receipt) - Multi-currency support via Strike (USD, EUR, GBP, AUD, BTC, USDT) --- ### v1.1.0 — Security Audit & Quality Hardening (February 2026) Comprehensive security audit and code quality pass across the entire API surface. Added 244 new tests, bringing total coverage from 407 to 651 tests. #### Security Hardening (7 Critical + 13 High Priority) **Critical fixes:** - **SSRF protection** on webhook callback URLs and proxy target URLs — block internal/private network ranges - **Webhook signature verification** — constant-time HMAC comparison to prevent timing attacks - **L402 amount binding** — macaroon caveats now bind to the invoiced amount, preventing underpayment exploits - **Subscription enforcement** — active Stripe subscription required for all authenticated API operations - **Error sanitization** — internal exception details, stack traces, and infrastructure information no longer leak in API responses - **API key hashing** — merchant API keys stored as one-way hashes for improved credential security - **Webhook replay protection** — timestamp validation rejects stale webhook deliveries **High priority fixes:** - Rate limiting on authentication, invoice creation, and webhook endpoints - Idempotency keys on all OpenNode charge creation calls to prevent duplicate invoices - Request size limits on all endpoints accepting request bodies - Input validation on all merchant-supplied URLs (scheme, length, format) - Correlation ID propagation via `X-Correlation-Id` header for end-to-end request tracing - Webhook delivery queue hardening with dead-letter handling and bounded retries - L402 macaroon expiry enforcement and nonce replay protection - Proxy target URL allowlist validation against merchant-registered domains - Merchant-scoped database query enforcement across all repository methods - Stripe webhook signature verification on all subscription lifecycle events - OpenNode API key validation on merchant registration - Secure cache headers on all API responses - Admin endpoint authentication audit and header validation #### API Improvements (10 Medium Priority) - OpenAPI annotations on all endpoints with response type documentation - Cancellation token propagation through all async controller actions and service methods - DTO validation attributes on all request models (required fields, range constraints, URL format) - Structured logging with Serilog semantic properties across all services - Consistent error response format using `ProblemDetails` (RFC 9457) - Health check endpoint expanded with dependency status (database, OpenNode, Stripe) - Webhook delivery status tracking with queryable history per merchant - Rate-limited (429) responses include a `retryAfter` hint (seconds) in the JSON error body - Request/response logging middleware with PII redaction - Graceful shutdown handling for in-flight webhook deliveries and background jobs #### Code Quality (5 Low Priority) - Dead code removal across controllers, services, and middleware - Consistent `async`/`await` patterns — eliminated fire-and-forget calls - Nullable reference type annotations on all public API surfaces - Standardized exception hierarchy with domain-specific exception types - Code style enforcement via `.editorconfig` and analyzer rules #### Test Coverage - **244 new tests** added (407 to 651 total) - Security-focused tests: SSRF blocking, signature verification, replay protection, rate limiting - Integration tests for full webhook delivery pipeline - L402 protocol tests covering challenge, payment, and verification flows - Subscription enforcement tests across all plan tiers - Edge case coverage for concurrent requests, timeout handling, and malformed input --- ## MCP Server ### Tool Consolidation — Lite/Standard/Full Profiles (MCP 2.0.0, September 2026) The one-tool-per-operation surface is replaced by three selectable profiles, set with `LIGHTNING_ENABLE_TOOL_PROFILE`: **`lite`** (`pay_invoice`, `access_l402_resource`, `get_balance`, `budget`, `receipts`, `setup_wallet`), **`standard`** (the new default — adds `pay_l402_challenge`, `test_l402_payment`, `create_invoice`, `check_invoice_status`, `verify_confirmation_code`, `discover_api`, `create_lightning_enable_account`, `wallet_ops`, `l402_producer`, `agent_services`), and **`full`** (`standard` plus every pre-consolidation tool name as a deprecated alias, for prompts and integrations written before this change). - **New consolidated, action-based tools:** `budget` (`action="status"|"tighten"`, replaces `get_budget_status` + `configure_budget`), `receipts` (`source="durable"|"session"`, replaces `get_receipts` + `get_payment_history`), `wallet_ops` (`action="price"|"exchange"|"send_onchain"`, replaces `get_btc_price` + `exchange_currency` + `send_onchain`), `l402_producer` (`action="create"|"verify"|"configure_receive"|"status"|"create_proxy"|"add_endpoint"|"publish"|"list_challenges"`, replaces `create_l402_challenge` + `verify_l402_payment` and adds six new producer-setup actions), and `agent_services` (`action="discover"|"request"|"settle"|"publish"|"unpublish"|"attest"|"reputation"`, replaces all seven ASA tools). - **New tool: `setup_wallet`** — configures `~/.lightning-enable/config.json` (wallet credential, spend ceiling) without hand-editing JSON. - **All 18 pre-consolidation tool names** (the 16 above plus the pre-existing `check_wallet_balance`/`get_all_balances`/`confirm_payment` aliases from v1.17.0) remain callable as deprecated aliases under `LIGHTNING_ENABLE_TOOL_PROFILE=full`, scheduled for removal in **v3.0.0**. - **Out-of-band confirmation gains a `confirmation.channel` config option** (`stderr` default, `refuse`, `webhook`, `file`), and `LIGHTNING_ENABLE_HOSTED=1` defaults a non-TTY process to `refuse` instead of `stderr`. - **Sats-native budget keys** — `limits.maxPerPaymentSats` / `limits.maxPerSessionSats` and `tiers.autoApproveSats` — sit alongside the existing USD-denominated ones for operators who'd rather not depend on the live BTC price feed for their ceiling. - The durable receipt log is now also exposed as an MCP **Resource** at `lightning-enable://receipts`. - See the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) for the full current tool list and the [deprecated alias table](/products/agentic-commerce/mcp-complete-guide#deprecated-aliases). ### v1.24.0 – v1.24.1 — Modern MPP "Payment" Scheme + Discovery Fixes (August 2026) - **MPP draft-00 client support, both ports:** modern `Payment` challenges (`draft-httpauth-payment-00`) are parsed and answered with the single-use `Authorization: Payment ` credential, with client-side safety checks (expiry, `intent: charge`, sat currency, amount-vs-invoice agreement). `access_l402_resource` surfaces the server's `Payment-Receipt`; `pay_l402_challenge` accepts a raw challenge via `challengeHeader` / `challenge_header`. - **`discover_api` probe alignment (v1.24.1):** manifests are also probed at `/.well-known/l402.json`, and protocol signposts are no longer mistaken for manifests. - NWC multi-relay failover on connect (.NET). ### v1.17.0 – v1.23.x — Tool Consolidation, Marketplace, Receipts (July–August 2026) - **Renamed/merged tools (v1.17.0):** `confirm_payment` → `verify_confirmation_code` (it only ever *verified* a code — it never moved money), and `check_wallet_balance` + `get_all_balances` → `get_balance` (a single tool returning the superset of both). Old names keep working as hidden deprecated aliases until **v2.0.0**; responses carry a `deprecated` marker. - **`unpublish_agent_capability`** joined the ASA surface (take a listing down: retires the L402 proxy, publishes a NIP-09 deletion + status=removed replacement). Canonical inventory since then: **26 tools = 17 out-of-the-box + 9 producer/ASA** — see the [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide). - **`get_receipts`** — durable local receipt log for payments made through the server. - Removed the legacy `L402_MAX_SATS_PER_REQUEST` / `L402_MAX_SATS_PER_SESSION` environment variables; operator limits live in `~/.lightning-enable/config.json` (USD-denominated), with `configure_budget` tighten-only at runtime. - NWC hardening: multi-relay connection strings (Alby Hub), SSRF connect guards, encryption auto-detect refinements. ### v1.15.0 — Self-Serve Signup (July 2026) - **`create_lightning_enable_account`** — self-bootstrapping signup: pays the ~100-sat L402 Fast Lane challenge (`POST /api/signup/l402`) with the connected wallet, returns a merchant API key with a 30-day trial, and writes it to `~/.lightning-enable/config.json` so the producer/ASA tools unlock on next restart. No card, no browser. ### v1.12.13 — Destination-Bound Confirmation Codes (June 2026) Security release — update both packages. - **Confirmation codes are now bound to the payment destination**, in addition to the amount and tool: the BOLT11 invoice (`pay_invoice`, `pay_l402_challenge`), the resource URL (`access_l402_resource`), the on-chain address (`send_onchain`), and the settlement endpoint (`settle_agent_service`, Python). - Closes an anti-redirect hole: previously a prompt-injected agent could obtain a code for a benign payment, then re-call the same tool at the same amount with a **different destination**. Destination mismatch now rejects without consuming the code, so the legitimate retry still works. - Applies to **both** the .NET and Python packages. --- ### v1.12.12 — Python Funds-Safety Parity (June 2026) Brings the **Python** package in line with the .NET out-of-band confirmation that shipped in v1.12.10. - **Out-of-band confirmation now in Python too.** Above-threshold payments (`pay_invoice`, `access_l402_resource`, `pay_l402_challenge`) print the confirmation code to the server console/stderr only — never in a tool result — so a prompt-injected agent can't read its own code and self-approve. Before this release the Python package still accepted an agent-supplied confirmation flag. - **`send_onchain` always confirms and fails closed** in Python (parity with .NET): no budget service, or any budget-check error, refuses the send. - **Confirmation parameter unified** across both packages — `.NET` uses `confirmationNonce`, Python uses `confirmation_nonce` (same concept, each language's casing). - The legacy in-process budget manager now fails closed above the auto-approve floor instead of allowing a self-confirm. --- ### v1.12.11 — Python Packaging Fix (June 2026) - `secp256k1` is now an **optional** dependency. `pip install lightning-enable-mcp` works on **every platform, including Windows**, with no build toolchain. - **Nostr Wallet Connect (NWC)** wallets need the optional extra: `pip install lightning-enable-mcp[nwc]`. Using an NWC wallet without it raises a clear error telling you to install it. Other wallet types (LND, Strike, OpenNode) don't need the extra. - The .NET package is unaffected — it uses managed crypto. --- ### v1.12.10 — Funds-Safety Hardening (June 2026) Hardens the agent payment path so a prompt-injected agent can't approve its own large payments. - **Out-of-band confirmation** *(.NET in this release; Python parity in v1.12.12)*. When a payment exceeds the auto-approve threshold, the server prints a confirmation code to its **console / stderr** — visible to the human operator and **never returned in a tool result**. The agent must ask the human for the code, then re-call the original payment tool with its confirmation-nonce parameter (`confirmationNonce` in .NET, `confirmation_nonce` in Python) to proceed. (The separate `confirm_payment` tool only *verifies* a code — it does not execute the payment.) Applies to `pay_invoice`, `access_l402_resource`, and `pay_l402_challenge`. This assumes the AI runtime can't read the server's stderr/logs; for agents that share a shell/host with the server, run it where the agent can't read its stderr. - **`send_onchain` always requires confirmation** (on-chain payments are irreversible), even for small amounts, and **fails closed** if the budget service is unavailable. - **Confirmation codes are bound to the exact amount AND tool** they approved — no cross-tool or cross-amount reuse. - **`configure_budget` added to the .NET server** (previously Python-only) and is **tighten-only in both packages**: an agent can lower its per-request / per-session caps at runtime but can never raise them above the operator's `~/.lightning-enable/config.json` limits. - **Budget checks fail closed** if the BTC price feed is unavailable (3 price sources, no stale fallback). - The NWC response preimage is no longer logged. --- ### v1.12.2 — Critical: Payment Confirmation Fix (April 2026) **Severity:** Critical — affects all .NET MCP clients since v1.6.0 **Problem:** When a client reports MCP elicitation capability (as Claude Code does) but elicitation doesn't actually work, the payment confirmation flow returned "Payment cancelled by user" with no nonce and no recovery path. Any payment above the auto-approve threshold was permanently blocked. **Fix:** Always fall back to nonce-based confirmation when elicitation fails, regardless of reported client capabilities. Affected tools: `pay_invoice`, `access_l402_resource`, `pay_l402_challenge`. **Update immediately:** ```bash # .NET global tool dotnet tool update -g LightningEnable.Mcp ``` **Affected versions:** v1.6.0 through v1.12.1 (all deprecated on NuGet). Python package was never affected. --- ### v1.11.2 — Version Bump (March 2026) - Version bump for CI pipeline alignment; no functional changes --- ### v1.11.1 — L402 HTTP Client Fix (March 2026) - Fixed gzip decompression issue in the L402 HTTP client by sending `Accept-Encoding: identity`, preventing decompression errors on some servers --- ### v1.11.0 — NIP-44 v2 Outgoing Encryption (March 2026) - NWC outgoing requests now encrypted with NIP-44 v2 (Alby Hub compatibility) - NIP-47 encryption tag support for improved NWC interoperability --- ### v1.10.1 — Docker Image Update (March 2026) - Updated Docker base image; no functional changes --- ### v1.10.0 — NIP-44 v2 Incoming Decryption (March 2026) - Auto-detects NIP-04 vs NIP-44 v2 encryption on incoming NWC responses - No configuration required — works transparently with all supported NWC wallets --- ### v1.9.0 — Producer Tools (March 2026) - **`create_l402_challenge`** — AI agents can now sell services: create a Lightning invoice + macaroon to present to other agents or users as a 402 challenge - **`verify_l402_payment`** — Verify an L402 token (macaroon + preimage) to confirm payment before granting access - Both tools require an Agentic Commerce subscription (from $99/mo) --- ### v1.8.0 — LND Wallet Support (February 2026) - Added LND REST API wallet backend for self-hosted nodes - Configure with `LND_REST_HOST` and `LND_MACAROON_HEX` env vars - Full L402 preimage support via LND --- ### v1.7.0 — `discover_api` Tool (February 2026) - **`discover_api`** — Search the L402 API registry by keyword/category, or fetch a specific API's manifest from a URL - Budget-aware annotations show how many calls you can afford at the current BTC price --- ### v1.6.0 — License Removed, All Consumer Tools Free (February 2026) - **License requirement removed** — all 15 consumer tools are now free, no Lightning payment or subscription required - Added **`confirm_payment`** tool for explicit payment confirmation before execution - The 6,000-sat license purchase from v1.5.0 is no longer needed; existing licenses are ignored --- ### v1.5.2 — Version Display Fix (February 2026) - Assembly version now matches package version for accurate startup display - Includes all v1.5.1 fixes below --- ### v1.5.1 — Critical Bug Fix (February 2026) **Severity:** Critical — affects all payment confirmations **Problem:** The `confirm_payment` tool was consuming the payment nonce before `pay_invoice` could use it. This caused every confirmed payment to fail with: ``` Invalid, expired, or already-used confirmation nonce ``` **Root Cause:** Both `confirm_payment` and `pay_invoice` called `ValidateAndConsumeConfirmation()`, which removes the nonce from memory. Since `confirm_payment` runs first (to get user approval), it consumed the nonce, leaving nothing for `pay_invoice` to validate against. **Fix:** `confirm_payment` now uses a read-only `ValidateConfirmation()` method that checks the nonce without consuming it. Only `pay_invoice` consumes the nonce. **Update immediately:** ```bash # .NET global tool dotnet tool update -g LightningEnable.Mcp ``` **Affected versions:** v1.5.0 --- ### v1.5.0 — Multi-Wallet Support - Added Strike wallet backend with preimage support - Added LND REST wallet backend - Added Nostr Wallet Connect (NWC) wallet backend - Configurable wallet priority via `WALLET_PRIORITY` env var or config file - Config file support at `~/.lightning-enable/config.json` - L402 license purchase via Lightning payment (6,000 sats, valid forever) - Budget controls with dual USD/sats limits --- ### v1.4.0 — L402 Auto-Pay - `access_l402_resource` tool for automatic L402 payment - `pay_l402_challenge` tool for manual L402 payment - Budget enforcement with per-request and per-session limits - Payment history tracking --- ### v1.3.0 — Initial Release - `pay_invoice` — Pay any Lightning invoice - `check_wallet_balance` — Check wallet balance - `get_payment_history` — View payment history - `get_budget_status` — View budget limits - OpenNode wallet backend ============================================================================== # Settlement Flows Source: https://docs.lightningenable.com/settlement-flows ============================================================================== # Settlement Flows This section documents the technical flows for different settlement patterns. ## Direct Settlement Flow Standard settlement via the REST API. ``` ┌─────────┐ ┌─────────────────┐ ┌──────────┐ ┌─────────┐ │ Client │ │ Lightning Enable│ │ Provider │ │Lightning│ └────┬────┘ └────────┬────────┘ └────┬─────┘ └────┬────┘ │ │ │ │ │ POST /api/payments │ │ │ │───────────────────>│ │ │ │ │ Create charge │ │ │ │───────────────────>│ │ │ │ │ Invoice │ │ │<───────────────────│ │ │ Invoice + ID │ │ │ │<───────────────────│ │ │ │ │ │ │ │ [Client pays invoice via Lightning wallet] │ │ │ │ │ │ │ Webhook: paid │ │ │ │<───────────────────│ │ │ Webhook: settled │ │ │ │<───────────────────│ │ │ │ │ │ │ ``` ### Endpoints Settlements ride on the [Payments API](/api-reference/payments) — there is no separate settlements endpoint: - `POST /api/payments` - Create an invoice (a settlement request) - `GET /api/payments/{invoiceId}` - Check settlement status - `POST /api/payments/{invoiceId}/sync` - Force a status sync from your payment provider - `POST /api/webhooks/strike` / `POST /api/webhooks/opennode` - Where your payment provider notifies Lightning Enable; Lightning Enable then forwards a signed webhook to your callback URL ## L402 Settlement Flow Per-request settlement using HTTP 402 challenges. ``` ┌─────────┐ ┌─────────────────┐ ┌──────────┐ │ Client │ │ Lightning Enable│ │ Provider │ └────┬────┘ └────────┬────────┘ └────┬─────┘ │ │ │ │ GET /resource │ │ │───────────────────>│ │ │ │ Create charge │ │ │───────────────────>│ │ │<───────────────────│ │ 402 + Invoice │ │ │<───────────────────│ │ │ │ │ │ [Client pays, receives preimage] │ │ │ │ │ GET /resource │ │ │ Auth: L402 token │ │ │───────────────────>│ │ │ │ Verify preimage │ │ 200 + Content │ │ │<───────────────────│ │ │ │ │ ``` ### L402 Header Format Request: ``` Authorization: L402 : ``` Challenge response (402): ``` WWW-Authenticate: L402 macaroon="", invoice="" ``` ## Webhook Flow Settlement confirmation via webhooks. ``` ┌──────────┐ ┌─────────────────┐ ┌──────────┐ │ Provider │ │ Lightning Enable│ │ Your App │ └────┬─────┘ └────────┬────────┘ └────┬─────┘ │ │ │ │ POST /webhooks │ │ │ (settlement event) │ │ │────────────────────>│ │ │ │ Verify signature │ │ │ Update state │ │ │ │ │ │ POST /your-webhook │ │ │────────────────────>│ │ │ │ Process │ │ 200 OK │ │ │<────────────────────│ │ 200 OK │ │ │<────────────────────│ │ │ │ │ ``` ### Webhook Signature Verification ``` X-LightningEnable-Signature: t={unix_timestamp},v1={hmac_sha256_hex} ``` Verify by computing HMAC-SHA256 of `{timestamp}.{payload}` using your webhook secret. Reject signatures older than 5 minutes. See [Webhook Verification](/api-reference/webhooks#verifying-webhooks) for full details and code examples. ## Settlement States | State | Description | Terminal | |-------|-------------|----------| | `unpaid` | Invoice created, awaiting settlement | No | | `processing` | Settlement detected, confirming | No | | `paid` | Settlement complete | Yes | | `expired` | Invoice expired | Yes | | `refunded` | Settlement was refunded | Yes | These are the same status values returned by the [Payments API](/api-reference/payments#payment-statuses) and forwarded in [webhooks](/api-reference/webhooks). ## Further Reading - [API Reference](/api-reference/overview) - Complete API documentation - [Webhooks](/api-reference/webhooks) - Webhook implementation details - [L402 Protocol](/products/agentic-commerce/how-it-works) - L402 technical details ============================================================================== # Strike Account Setup Source: https://docs.lightningenable.com/strike-setup/account-setup ============================================================================== # Strike Account Setup Strike is a payments platform that supports Bitcoin Lightning and on-chain payments. Lightning Enable connects to your Strike account via API to create invoices and process payments. :::tip Default Provider Strike is the default payment provider for Lightning Enable — merchants who make no explicit choice are served by Strike. It supports preimage extraction for L402, accepts multi-currency invoices (USD, EUR, GBP, BTC), and requires no KYB to get started. ::: ## Why Strike? Strike provides: - **Lightning Network support** — Instant Bitcoin payments - **On-chain Bitcoin** — Accept on-chain payments alongside Lightning - **Multi-currency** — Create invoices in USD, EUR, GBP, or BTC - **No per-transaction fee** — No processing fee for receiving Lightning payments - **Preimage support** — Required for L402 protocol (pay-per-request) - **Instant setup** — No KYB required to start accepting payments Lightning Enable uses Strike as a payment provider, giving you: - API middleware architecture (we never touch funds — Strike facilitates custody and settlement) - Bring Your Own API Key (BYOA) model - Full control over your payment settings ## Create Strike Account ### Step 1: Sign Up 1. Visit [dashboard.strike.me](https://dashboard.strike.me) 2. Create an account or sign in 3. Complete identity verification if prompted ### Step 2: Generate an API Key 1. Go to **API Keys** in the Strike dashboard 2. Click **Create API Key** 3. Name your key (e.g., "Lightning Enable Production") 4. Enable the following scopes: - `partner.receive-request.read` — Read receive requests (invoices) - `partner.receive-request.create` — Create receive requests - `partner.webhooks.manage` — Manage webhook subscriptions 5. Click **Create** and copy your API key :::note Scope Labels Strike's dashboard sometimes labels the second scope "write" rather than "create". The scope identifier Strike actually issues is `partner.receive-request.create` — grant whichever label allows **creating** receive requests. ::: :::tip Not sure? We'll set it up with you Scopes are the step people most often get wrong, and a key missing one fails quietly at your first real payment. If anything here is unclear, email [support@lightningenable.com](mailto:support@lightningenable.com) before you go live — a real person reads it, and we're happy to walk through the setup with you. ::: :::warning Save Your API Key Strike only shows the full API key once at creation. Copy it immediately and store it securely. If lost, you'll need to create a new key. ::: ### Step 3: Configure in Lightning Enable 1. Log in to the [Lightning Enable dashboard](https://api.lightningenable.com/dashboard) 2. Go to **Settings** 3. Under **Payment Provider**, select **Strike** 4. Paste your Strike API key 5. Click **Save Key** 6. Click **Validate** to verify the key works ## API Key Scopes All three scopes above are required. [API Keys](/strike-setup/api-keys#required-scopes) is the reference for what each one does, how to rotate keys, and why validation passing does not prove your scopes are correct. Lightning Enable automatically registers a webhook subscription on your first payment to receive real-time payment notifications. ## Environments Lightning Enable's hosted platform connects to Strike's production environment: | | Details | |---|---------| | **API URL** | `https://api.strike.me/v1` | | **Dashboard** | `https://dashboard.strike.me` | | **Bitcoin** | Real mainnet Bitcoin | :::info Testing on the Hosted Platform Strike does offer a sandbox environment (`https://api.dev.strike.me/v1`), but the Strike API base URL on Lightning Enable's hosted platform is a platform-wide setting pointed at production — it is not selectable per merchant. The practical way to test your integration is to create a small real invoice (e.g., $1 USD) and pay it with a Lightning wallet. ::: ## How Payments Work with Strike When a customer pays through Lightning Enable with Strike as the provider: 1. Lightning Enable calls Strike's **receive-requests** API to create an invoice 2. Strike returns both a **Lightning invoice** (BOLT11) and an **on-chain Bitcoin address** 3. The customer chooses to pay via Lightning or on-chain 4. Strike sends a **webhook** when payment is received 5. Lightning Enable updates the payment status and notifies your platform ### Payment Methods | Method | Speed | Best For | |--------|-------|----------| | **Lightning** | Instant (~1 second) | Small-to-medium payments | | **On-chain** | ~10+ minutes (1 confirmation) | Larger payments, fallback | Both methods are presented automatically on the payment page — the customer chooses. ## Refunds with Strike Strike does not have a dedicated refund API. When you initiate a refund for a Strike payment through Lightning Enable: - **Lightning refunds** — Processed as a new outgoing Lightning payment to the customer - **On-chain refunds** — Processed as a new outgoing on-chain payment to the customer This is handled transparently by Lightning Enable's refund service. ## Account Security ### API Key Best Practices - Never share your API keys publicly - Store a copy only in a password manager or secret store — Lightning Enable encrypts your key at rest (AES-256-GCM) once you save it in the dashboard - Create separate keys for different integrations if needed - Rotate keys periodically - Revoke unused keys in the Strike dashboard ## Troubleshooting ### API Key Validation Failed If validation reports a failure in the Lightning Enable dashboard: 1. Verify the key was copied correctly (no extra spaces) 2. Check that the key hasn't been revoked in Strike dashboard 3. Try creating a new API key with all three required scopes :::note Validation Does Not Check Scopes Validation reads your Strike account profile, so a key missing one or more of the three required scopes still validates successfully. A scope gap surfaces later — as a failed invoice or a webhook that never arrives. See [API Keys](/strike-setup/api-keys#required-scopes). ::: ### Payments Not Being Created 1. Verify your Strike API key has the receive-request write/create scope (`partner.receive-request.create`) 2. Check the Lightning Enable dashboard for error messages 3. Ensure your Lightning Enable subscription is active ### Webhooks Not Arriving Lightning Enable auto-registers webhooks on first payment. If status updates aren't arriving: 1. Check your Strike dashboard for webhook subscriptions 2. Verify your API key has `partner.webhooks.manage` scope 3. Try creating a new payment to trigger re-registration ## Next Steps Once your Strike account is configured: - [Webhooks](/api-reference/webhooks) — Receive signed payment notifications at your callback URL (Lightning Enable auto-registers the Strike subscription on your first payment) - [First Payment](/getting-started/first-payment) — Test your integration end to end with a small payment - [Product Overview](/products/product-overview) — See all Lightning Enable products - [Agentic Commerce](/products/agentic-commerce/overview) — Set up pay-per-request APIs - [Dashboard Guide](/products/agentic-commerce/dashboard-guide) — Configure your first proxy ============================================================================== # API Keys Source: https://docs.lightningenable.com/strike-setup/api-keys ============================================================================== # Strike API Keys Your Strike API key lets Lightning Enable create invoices and read payment status on your behalf. Lightning Enable stores the key encrypted and never returns it back out of the API. Strike is the default payment provider. If you have not created your Strike account yet, start with [Strike Account Setup](/strike-setup/account-setup). ## Required Scopes Lightning Enable needs three scopes. Grant all three. :::warning Validation Does Not Check Scopes `POST /api/merchant/validate-strike` confirms your key authenticates against Strike by reading your account profile. It does **not** exercise any of the scopes below, so a key with none of them still validates successfully. A missing scope surfaces later, as a failure on your first real invoice or a webhook that never arrives. Grant all three at creation rather than relying on validation to catch a gap. ::: | Scope | Purpose | Required? | |-------|---------|-----------| | `partner.receive-request.read` | Check invoice and payment status | Yes | | `partner.receive-request.create` | Create Lightning invoices | Yes | | `partner.webhooks.manage` | Auto-register the webhook subscription | Yes | :::note Scope Labels Strike's dashboard sometimes labels this scope "write" rather than "create". The identifier Strike issues is `partner.receive-request.create` — grant whichever label allows **creating** receive requests. ::: :::tip Stuck on scopes? Ask us This is the step people most often get wrong, and validation will not catch it (see the warning above). Email [support@lightningenable.com](mailto:support@lightningenable.com) with what you are seeing in the Strike dashboard and we will tell you exactly which boxes to tick — or set it up with you on a call if that is easier. ::: ## Generate a Key 1. Sign in to [dashboard.strike.me](https://dashboard.strike.me) 2. Go to **API Keys** 3. Click **Create API Key** 4. Name the key so you can identify it later, for example `Lightning Enable Production` 5. Enable all three scopes from the table above 6. Click **Create** and copy the key :::warning Strike Shows the Key Once Strike displays the full API key only at creation. Copy it immediately and store it in a password manager or secret store. If you lose it, create a new key — you cannot recover the original. ::: ## Configure in Lightning Enable There are two ways to save your key, and no others. There is no environment variable or configuration file to set. ### Using the Dashboard 1. Sign in to the [Lightning Enable dashboard](https://api.lightningenable.com/dashboard) 2. Go to **Settings** 3. Under **Payment Provider**, select **Strike** 4. Paste your key and click **Save Key** 5. Click **Validate** to confirm the key works ### Using the Merchant API Save the key: ```bash curl -X PUT https://api.lightningenable.com/api/merchant/strike-key \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{ "strikeApiKey": "your-strike-api-key" }' ``` Validate it: ```bash curl -X POST https://api.lightningenable.com/api/merchant/validate-strike \ -H "X-API-Key: your-merchant-api-key" ``` Saving a Strike key through either method above already sets Strike as your provider if you had not chosen one. To set it explicitly at any time: ```bash curl -X PUT https://api.lightningenable.com/api/merchant/payment-provider \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{ "provider": "strike" }' ``` The `provider` field accepts `strike` or `opennode`. :::info Provider Resolution Lightning Enable resolves your provider in this order: 1. An explicit choice on your account wins. 2. With no explicit choice and exactly **one** provider key saved, that key's provider is used. 3. Otherwise — including when you have **both** a Strike and an OpenNode key saved but no explicit choice — Lightning Enable falls back to the platform default, which is Strike. Case 3 is the one that surprises merchants migrating from OpenNode: keeping both keys does not keep you on OpenNode. Set the provider explicitly if you want a specific lane. ::: ## Key Security - Configure the key only through the dashboard or `PUT /api/merchant/strike-key`. Never commit it to source control or paste it into client-side code. - Lightning Enable encrypts your key at rest with AES-256-GCM and never returns it from the API once saved. - Keep any copy of the key in a password manager or secret store. - If you suspect the key leaked, revoke it in the Strike dashboard immediately, then save a new one. - Enable two-factor authentication on your Strike account. ## Key Rotation Rotate your key every 90 days, after someone with access leaves, or any time you suspect exposure. 1. Create a new key in the Strike dashboard with all three scopes 2. Save it in Lightning Enable through the dashboard or `PUT /api/merchant/strike-key` 3. Validate it with `POST /api/merchant/validate-strike` 4. Confirm a small real payment succeeds — see [Testing](/strike-setup/testing) 5. Revoke the old key in the Strike dashboard The update takes effect immediately. Subsequent payments use the new key, so there is no downtime window to manage. ## Checking Key Status To confirm a key is saved without revealing it: ```bash curl -X GET https://api.lightningenable.com/api/merchant/me \ -H "X-API-Key: your-merchant-api-key" ``` The response reports whether a key is present, not the key itself. ## Troubleshooting ### Validation Reports `isValid: false` `POST /api/merchant/validate-strike` returns HTTP `200` whether or not the key works — read the `isValid` field in the body rather than the status code. If it reports `false`: 1. Check the key was copied without extra spaces 2. Check the key has not been revoked in the Strike dashboard 3. Confirm a key is actually saved — the same response reports when none is configured 4. Create a new key and save it again Note that validation passing tells you nothing about scopes. See [Required Scopes](#required-scopes). ### Invoices Are Not Created 1. Confirm the key has `partner.receive-request.create` 2. Check that your Lightning Enable subscription is active 3. Look for error detail in the Lightning Enable dashboard ### Webhooks Do Not Arrive Lightning Enable registers the Strike webhook subscription automatically on your first payment. If status updates never arrive: 1. Confirm the key has `partner.webhooks.manage` 2. Check your Strike dashboard for a webhook subscription 3. Create another payment to trigger re-registration For more detail, see [Webhooks](/strike-setup/webhooks). ## Next Steps - [Webhooks](/strike-setup/webhooks) — Understand both webhook hops and verify signatures - [Testing](/strike-setup/testing) — Confirm your integration end to end - [First Payment](/getting-started/first-payment) — Take your first payment ============================================================================== # Testing Source: https://docs.lightningenable.com/strike-setup/testing ============================================================================== # Testing Your Strike Integration Work through these checks in order. Each one isolates a different failure point, so the first one that fails tells you where the problem is. ## Before You Start :::info The Hosted Platform Uses Strike Production Strike offers a sandbox at `https://api.dev.strike.me/v1`, but the Strike API base URL on Lightning Enable's hosted platform is a platform-wide setting pointed at production. It is not selectable per merchant. That means testing uses **real mainnet Bitcoin**. Use small amounts — $1 USD or a few hundred sats is enough to prove every step. ::: You need: - A Strike API key saved in Lightning Enable, with all three required scopes - Your Lightning Enable merchant API key - A Lightning wallet holding a small balance ## Step 1: Validate the Key ```bash curl -X POST https://api.lightningenable.com/api/merchant/validate-strike \ -H "X-API-Key: your-merchant-api-key" ``` This returns HTTP `200` whether or not the key works. **Read the `isValid` field in the body** — do not gate on the status code, and do not use `curl -f` here. `"isValid": true` means Lightning Enable authenticated against Strike with your key. It does **not** mean your scopes are correct: validation reads your account profile and exercises none of the three required scopes. A scope gap shows up at Step 2 or Step 4 instead. If `isValid` is `false`, the problem is the key itself — see [API Keys](/strike-setup/api-keys#troubleshooting). ## Step 2: Create a Small Invoice ```bash curl -X POST https://api.lightningenable.com/api/payments \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{ "orderId": "strike-test-001", "amount": 1.00, "currency": "USD", "description": "Strike integration test" }' ``` `orderId` is required and must be 1–100 characters. It is your own reference for the payment. The response includes an invoice ID and a BOLT11 Lightning invoice. If this fails, read the error before changing anything: - A **validation error** naming a field means the request body is wrong — Lightning Enable never contacted Strike, and your key is not the problem. - A **provider error** means Strike rejected the call. That is where a missing `partner.receive-request.create` scope surfaces. :::note Description Length Strike accepts a `description` between 1 and 250 bytes on the invoice. Lightning Enable truncates longer descriptions rather than rejecting them, and substitutes a default when the description is blank. ::: ## Step 3: Pay the Invoice Pay the BOLT11 invoice with your Lightning wallet. Payment settles in about a second. ## Step 4: Confirm the Status Changed ```bash curl -X GET https://api.lightningenable.com/api/payments/{invoiceId}/status ``` The status should read as paid. If it still reads unpaid a minute after your wallet confirms, the Strike webhook is not reaching Lightning Enable — see [Webhooks](/strike-setup/webhooks#no-webhooks-arrive-from-strike). ## Step 5: Confirm Your Callback Fired If you configured a callback URL, check that your endpoint received a signed webhook and that your signature check passed. A delivery that arrives but fails verification is almost always a raw-body problem — see [Verify the Signature](/strike-setup/webhooks#verify-the-signature). ## Testing L402 If you are monetizing an API with L402, Strike is the provider you want. Strike returns the payment preimage on outgoing payments, which matters when an agent pays *from* your balance. Receiving L402 payments works on either provider — challenge creation and verification are provider-agnostic, and verification compares `SHA256(payer's preimage)` against the payment hash without asking your provider for anything. To see the protocol shape before wiring your own endpoint, request the public demo: ```bash curl -i https://api.lightningenable.com/l402/test/ping ``` You get a `402 Payment Required` with a challenge containing a macaroon and an invoice. Pay it, then retry with the token: ```bash curl -i https://api.lightningenable.com/l402/test/ping \ -H "Authorization: L402 {macaroon}:{preimage}" ``` :::warning This Endpoint Does Not Test Your Account `/l402/test/ping` mints and verifies against Lightning Enable's own test merchant. It does not read your API key, your Strike key, or your configuration — the route skips authentication entirely. A `200` here proves the protocol works, not that **your** setup works. It succeeds even if you have no Strike key saved, and it can return `503` for reasons that have nothing to do with your account. To test your own L402 configuration, create a proxy or a producer challenge on your own merchant and pay that. See [Agentic Commerce](/products/agentic-commerce/overview). ::: :::tip One Payment Cycle at a Time Finish one 402 to payment to access cycle before starting the next. Running L402 payments concurrently during testing makes failures much harder to attribute. ::: ## What to Check Before Going Live - [ ] Key validates, and all three scopes are granted - [ ] An invoice is created and paid successfully - [ ] Payment status updates without manual intervention - [ ] Your callback receives webhooks and signature verification passes - [ ] Your webhook handler is idempotent against duplicate deliveries - [ ] Your handler returns `2xx` promptly and does slow work afterward - [ ] For L402: a paid token grants access and an unpaid request is refused ## Troubleshooting ### Invoice Is Created but Never Settles Confirm you paid the BOLT11 invoice rather than an expired copy. Lightning invoices expire; create a fresh one and retry. ### Status Stays Unpaid After a Confirmed Payment The Strike webhook is not arriving. Confirm the `partner.webhooks.manage` scope, then create another payment to trigger re-registration. ### L402 Returns 503 A `503` with `challenge_persist_failed` means Lightning Enable could not durably record the challenge and deliberately issued no invoice. You were not charged. Retry, and if it persists, the platform is reporting a storage fault rather than a problem with your configuration. ### L402 Returns 414 Your resource path exceeds the 848-character limit for a resource identifier. Shorten the path. ## If Something Does Not Add Up You do not have to work through this alone. Email [support@lightningenable.com](mailto:support@lightningenable.com) with the step you are on and what you are seeing — including the correlation ID if you have one — and we will help you finish the integration. We would rather hear from you before your first real payment than after. ## Next Steps - [First Payment](/getting-started/first-payment) — Take a real payment end to end - [Agentic Commerce](/products/agentic-commerce/overview) — Monetize an API per request - [Troubleshooting](/troubleshooting) — Platform-wide diagnostics ============================================================================== # Webhooks Source: https://docs.lightningenable.com/strike-setup/webhooks ============================================================================== # Strike Webhooks Payment notifications travel in two hops. Understanding the split matters, because you configure one of them and Lightning Enable handles the other for you. ``` Strike ──(1)──▶ Lightning Enable ──(2)──▶ Your callback URL ``` | Hop | Who configures it | What it carries | |-----|-------------------|-----------------| | **1. Strike → Lightning Enable** | Lightning Enable, automatically | A thin notification with an entity ID | | **2. Lightning Enable → you** | You, in the dashboard | Full payment detail, HMAC signed | ## Hop 1: Strike to Lightning Enable You do not configure this hop. Lightning Enable registers a webhook subscription against your Strike account automatically, the first time it creates a payment for you. That is why your API key needs the `partner.webhooks.manage` scope. Lightning Enable stores the resulting subscription ID and a per-merchant signing secret so it can verify that incoming notifications genuinely came from Strike. :::info Strike Webhooks Are Thin A Strike webhook carries an entity ID, not the payment itself. Lightning Enable calls the Strike API to fetch the full details before acting on it. This is the main behavioral difference from OpenNode, whose webhooks carry the full payload. ::: The endpoint that receives these notifications is `POST /api/webhooks/strike`. It is public by necessity — Strike must reach it — and it authenticates every request by signature rather than by API key. ## Hop 2: Lightning Enable to Your Platform This is the hop you configure. Set a callback URL and Lightning Enable posts signed payment events to it. ### Set Your Callback URL In the dashboard, go to **Settings** and set your callback URL. Or use the merchant API: ```bash curl -X PUT https://api.lightningenable.com/api/merchant/webhook-url \ -H "X-API-Key: your-merchant-api-key" \ -H "Content-Type: application/json" \ -d '{ "webhookUrl": "https://your-platform.example.com/webhooks/lightning-enable" }' ``` Your endpoint must accept `POST`, be reachable over HTTPS, and return a `2xx` status quickly. Do the slow work after you respond. ### Verify the Signature Every forwarded webhook carries an `X-LightningEnable-Signature` header: ``` X-LightningEnable-Signature: t=1735689600,v1=5257a869e7bcd6742a1f2c... ``` `t` is a Unix timestamp and `v1` is an HMAC-SHA256 signature over `{t}.{raw_body}`. **Follow the reference implementations in [Verifying Webhooks](/api-reference/webhooks#verifying-webhooks).** That page is the single source of truth for this procedure and carries working [Node.js](/api-reference/webhooks#nodejs--javascript-verification), [C#](/api-reference/webhooks#c-verification), and [Python](/api-reference/webhooks#python-verification) verifiers, plus the exact freshness window under [Replay Protection](/api-reference/webhooks#replay-protection). This page deliberately does not repeat them — a second copy of a security-critical routine drifts out of sync with the first. Three things are worth calling out because they are the common ways a verifier goes wrong: - **Enforce timestamp freshness.** Signature validity alone does not stop a replay. Reject requests whose `t` falls outside the tolerance given under [Replay Protection](/api-reference/webhooks#replay-protection). - **Compare in constant time.** Use `CryptographicOperations.FixedTimeEquals` or your language's equivalent. A plain `==` returns on the first mismatched byte and leaks the expected signature to anyone measuring response times. - **Parse the header defensively.** Do not index into `split(',')` positionally. A malformed or hostile header should be rejected, not throw an unhandled exception out of your public endpoint. ### Use the Raw Body Compute the signature over the exact bytes you received. If your framework parses the JSON and you re-serialize it, key order or whitespace can change and the signature will not match. Capture the raw body before parsing. ## Idempotency Retries and network conditions mean the same event can arrive more than once. Key your handler on the payment or invoice identifier and make repeat deliveries a no-op. Do not assume exactly-once delivery. ## Troubleshooting ### No Webhooks Arrive from Strike 1. Confirm your Strike API key has the `partner.webhooks.manage` scope 2. Check the Strike dashboard for a webhook subscription 3. Create another payment — registration retries on payment creation 4. Re-save your Strike key, which forces re-registration ### Webhooks Arrive but Signature Checks Fail 1. Confirm you are hashing the raw body, not a re-serialized version 2. Confirm the signed payload is `{timestamp}.{body}`, not the body alone 3. Confirm you are using your Lightning Enable webhook secret, not your Strike API key 4. Compare the hex encoding — the signature is lowercase hex, not Base64 ### Your Endpoint Times Out Return `2xx` first and process afterward. Slow handlers look like failures and trigger retries. ## Next Steps - [Testing](/strike-setup/testing) — Confirm the full path with a real payment - [Webhooks API Reference](/api-reference/webhooks) — Full event payloads and retry behavior - [API Keys](/strike-setup/api-keys) — Scopes and rotation ============================================================================== # Agent SDKs & Agent Service Agreements Source: https://docs.lightningenable.com/tools/agent-sdks/overview ============================================================================== # Agent SDKs & Agent Service Agreements (ASA) **Agent Service Agreements (ASA)** let AI agents discover each other's paid services, request them, settle via [L402](/products/agentic-commerce/how-it-works) Lightning payments, and publish reputation — all over the open Nostr protocol. Lightning Enable ships three official Agent SDKs implementing ASA: | Language | Package | Install | |----------|---------|---------| | Python (≥3.10) | [`le-agent-sdk` on PyPI](https://pypi.org/project/le-agent-sdk/) | `pip install le-agent-sdk` | | TypeScript | [`le-agent-sdk` on npm](https://www.npmjs.com/package/le-agent-sdk) | `npm install le-agent-sdk` | | .NET 8 | [`LightningEnable.AgentSdk` on NuGet](https://www.nuget.org/packages/LightningEnable.AgentSdk) | `dotnet add package LightningEnable.AgentSdk` | All three are open source (MIT): [Python](https://github.com/refined-element/le-agent-sdk-python) · [TypeScript](https://github.com/refined-element/le-agent-sdk-ts) · [.NET](https://github.com/refined-element/le-agent-sdk-dotnet) ## The ASA Flow The shipped protocol is deliberately simple — four steps, four Nostr event kinds: ``` 1. DISCOVER Provider publishes a capability (kind 38400). Requester queries relays for capabilities. │ 2. REQUEST Requester publishes a service request (kind 38401) addressed to the provider — one event, stating the capability, a budget in sats, and parameters. │ 3. SETTLE Requester hits the provider's L402 endpoint: 402 challenge → pay Lightning invoice → retry with Authorization: L402 : → result. │ 4. ATTEST Requester publishes an attestation (kind 38403) rating the provider 1–5, tagged to the agreement. ``` :::important No negotiation loop There is **no automated offer / counter-offer / accept negotiation** in ASA. The request (kind 38401) is a single event; settlement terms come from the provider's advertised capability (or its agreement event, kind 38402, which carries the L402 endpoint when it isn't in the capability itself). Payment is proven cryptographically by the L402 preimage — no back-and-forth required. ::: ### Event Kinds | Kind | Event | Published by | Purpose | |------|-------|--------------|---------| | `38400` | Capability | Provider | Advertises a service: service ID (`d` tag), categories, pricing in sats, L402 endpoint. Addressable/replaceable (NIP-33 style) | | `38401` | Service Request | Requester | Requests a capability with a sats budget and key-value params | | `38402` | Agreement | Provider | The provider's response to a request — price and L402 settlement endpoint | | `38403` | Attestation | Requester | Signed 1–5 rating of a counterparty, tagged to the agreement event | ## The Relay The default relay is **`wss://agents.lightningenable.com`** — a [strfry](https://github.com/hoytech/strfry) Nostr relay operated by Lightning Enable that relays kinds 38400–38403. It is a plain relay: it stores and forwards events, nothing more. The SDKs accept any list of relay URLs, so you can run ASA over your own relays. ## Relationship to the MCP Server The [Lightning Enable MCP server](/products/agentic-commerce/mcp-complete-guide) exposes the same ASA protocol as the seven actions of one tool, `agent_services`, for AI agents (formerly seven separate tools — the old names still work as deprecated aliases under `LIGHTNING_ENABLE_TOOL_PROFILE=full`, removed in v3.0.0). **The MCP tool and these SDKs gate differently:** with the MCP tool, the `agent_services` actions `request`, `publish`, `unpublish`, and `attest` require a `LIGHTNING_ENABLE_API_KEY` (`discover`, `settle`, and `reputation` don't); with the SDKs, only the producer side needs a key — see [API Keys: What Needs One](#api-keys-what-needs-one) below: | `agent_services` action | Formerly (deprecated alias) | SDK equivalent | |---|---|---| | `discover` | `discover_agent_services` | `discover` | | `request` | `request_agent_service` | `request_service` | | `settle` | `settle_agent_service` | `settle` / `settle_via_l402` | | `publish` | `publish_agent_capability` | `publish_capability` | | `unpublish` | `unpublish_agent_capability` | — (dashboard or MCP only) | | `attest` | `publish_agent_attestation` | `publish_attestation` | | `reputation` | `get_agent_reputation` | `get_reputation_score` / `getReputation` | Use the MCP tools when your agent runs inside an MCP-capable host (Claude, Cursor, etc.); use the SDKs when you're writing agent code directly. ## Attestation & Reputation After settlement, the requester publishes a **kind 38403 attestation**: a Nostr event signed with the requester's key, tagged with the subject's pubkey (`p` tag), the agreement event ID (`e` tag), and a `rating` of 1–5, with free-text review content. Reputation is computed client-side: the SDKs query relays for attestations about a pubkey and average the ratings (Python `get_reputation_score` returns the average or `None`; TypeScript `getReputation` returns `{ average, count, attestations }`; .NET `GetReputationAsync` returns a `ReputationScore`). Because attestations are signed events on open relays, any counterparty can verify them independently — no central reputation database. ## API Keys: What Needs One - **Requester / consumer side** (discover, request, settle, attest, query reputation): **no Lightning Enable API key required.** You need a Nostr private key for signing events and a Lightning wallet (via a pay-invoice callback) to pay invoices. - **Producer / provider side** (`create_challenge`, `verify_payment` — minting and verifying L402 challenges through the [L402 Producer API](/products/agentic-commerce/l402-producer-api)): requires a Lightning Enable merchant API key with an [Agentic Commerce plan](/products/product-overview). As always, Lightning Enable does not hold funds — payments settle wallet-to-wallet over Lightning, and your payment provider facilitates custody and settlement on the producer side. ## Next Steps - [Quickstart](./quickstart.md) — provider and requester examples in all three languages - [L402 Producer API](/products/agentic-commerce/l402-producer-api) — the challenge/verify endpoints behind producer operations - [MCP Complete Guide](/products/agentic-commerce/mcp-complete-guide) — the tool-based route for MCP hosts ============================================================================== # Agent SDK Quickstart Source: https://docs.lightningenable.com/tools/agent-sdks/quickstart ============================================================================== import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Agent SDK Quickstart Two roles, one protocol: - **Provider** — advertise a capability (kind 38400), listen for requests (kind 38401), settle via L402. - **Requester** — discover capabilities, then settle directly against the capability's L402 endpoint. Read the [ASA overview](./overview.md) first for the flow and event kinds. ## Install ```bash pip install le-agent-sdk ``` Requires Python 3.10+. ```bash npm install le-agent-sdk ``` ```bash dotnet add package LightningEnable.AgentSdk ``` Requires .NET 8. You'll need a hex-encoded 32-byte **Nostr private key** for signing events. Producer operations (minting/verifying L402 challenges) additionally need a **Lightning Enable merchant API key** ([Agentic Commerce plan](/products/product-overview)); discovery, requesting, and settling do not. ## Provider: Publish a Capability and Serve Requests ```python import asyncio from le_agent_sdk import AgentManager, AgentCapability, AgentPricing async def main(): manager = AgentManager( private_key="", relay_urls=["wss://agents.lightningenable.com"], le_api_key="", # producer operations only ) # Advertise the service (kind 38400) capability = AgentCapability( service_id="translate-v1", categories=["ai", "translation"], content="AI translation. 50+ languages.", pricing=[AgentPricing(amount=10)], # 10 sats per request l402_endpoint="https://api.example.com/l402/translate", ) event_id = await manager.publish_capability(capability) print(f"Published capability {event_id}") # Listen for incoming service requests (kind 38401) async for request in manager.listen_requests(): print(f"Request from {request.pubkey}: " f"budget={request.budget_sats} sats, params={request.params}") # Respond by publishing an agreement (kind 38402) with your L402 # endpoint via manager.publish_agreement(...), or mint a challenge # directly with manager.create_challenge(...) and verify the # payment with manager.verify_payment(macaroon, preimage). asyncio.run(main()) ``` ```typescript import { AgentManager, AgentCapability, AgentPricing } from "le-agent-sdk"; const manager = new AgentManager({ privateKey: "", relayUrls: ["wss://agents.lightningenable.com"], leApiKey: "", // producer operations only }); // Advertise the service (kind 38400) const capability = new AgentCapability({ serviceId: "translate-v1", categories: ["ai", "translation"], content: "AI translation. 50+ languages.", pricing: [new AgentPricing({ amount: 10 })], // 10 sats per request l402Endpoint: "https://api.example.com/l402/translate", }); const eventId = await manager.publishCapability(capability); console.log(`Published capability ${eventId}`); // Listen for incoming service requests (kind 38401) for await (const request of manager.listenRequests()) { console.log( `Request from ${request.pubkey}: budget=${request.budgetSats} sats`, request.params ); // Respond by publishing an agreement (kind 38402) with your L402 // endpoint via manager.publishAgreement(...), or mint a challenge // directly with manager.createChallenge(...) and verify the payment // with manager.verifyPayment(macaroon, preimage). } ``` ```csharp using LightningEnable.AgentSdk.Agent; using LightningEnable.AgentSdk.Models; await using var manager = new AgentManager(new AgentManagerOptions { PrivateKey = "", // Always set RelayUrls explicitly — the library default is a public // relay, not the Lightning Enable agent relay. RelayUrls = new List { "wss://agents.lightningenable.com" }, LightningEnableApiKey = "", // producer ops only }); await manager.ConnectAsync(); // Advertise the service (kind 38400) var capability = new AgentCapability { DTag = "translate-v1", Name = "Translation Service", Description = "AI translation. 50+ languages.", PriceSats = 10, Endpoint = "https://api.example.com/translate", Categories = new List { "ai", "translation" }, }; var eventId = await manager.PublishCapabilityAsync(capability); Console.WriteLine($"Published capability {eventId}"); // When a request (kind 38401) arrives, mint an L402 challenge for it // and verify the payment once the requester pays: var challenge = await manager.CreateChallengeAsync( agreement, priceSats: 10, description: "Payment for translation"); // ...requester pays the Lightning invoice, presents the preimage: bool valid = await manager.VerifyPaymentAsync(challenge.Macaroon, preimage); ``` :::note The .NET SDK (0.3.2) does not include a request-listening stream like Python/TypeScript `listen_requests` — subscribe to kind-38401 events on the relay yourself, or run the provider loop in Python/TypeScript. See the [SimpleProvider example](https://github.com/refined-element/le-agent-sdk-dotnet/tree/main/examples) in the repo. ::: ## Requester: Discover and Settle ```python import asyncio from le_agent_sdk import AgentManager async def pay_invoice(invoice: str) -> str: # Pay the BOLT11 invoice with your Lightning wallet # and return the hex preimage. ... async def main(): manager = AgentManager( private_key="", relay_urls=["wss://agents.lightningenable.com"], pay_invoice_callback=pay_invoice, # enables L402 auto-payment ) # Discover capabilities (kind 38400) capabilities = await manager.discover(categories=["translation"], limit=10) best = capabilities[0] print(f"Using {best.service_id} at {best.pricing[0].amount} sats") # Optionally signal a request (kind 38401) so the provider sees it await manager.request_service( capability_event_id=best.event_id, provider_pubkey=best.pubkey, budget_sats=100, params={"target_lang": "es"}, ) # Settle directly against the capability's L402 endpoint: # 402 challenge -> pay via callback -> retry with preimage response = await manager.settle_via_l402( best, method="POST", json={"text": "Hello", "target_lang": "es"} ) print(response.status_code, response.text) asyncio.run(main()) ``` ```typescript import { AgentManager } from "le-agent-sdk"; const manager = new AgentManager({ privateKey: "", relayUrls: ["wss://agents.lightningenable.com"], // Enables L402 auto-payment: pay the BOLT11 invoice with your // Lightning wallet and return the hex preimage. payInvoiceCallback: async (invoice) => payWithMyWallet(invoice), }); // Discover capabilities (kind 38400) const capabilities = await manager.discover({ categories: ["translation"], limit: 10, }); const best = capabilities[0]; console.log(`Using ${best.serviceId} at ${best.pricing[0]?.amount} sats`); // Optionally signal a request (kind 38401) so the provider sees it await manager.requestService(best.eventId, best.pubkey, 100, { target_lang: "es", }); // Settle directly against the capability's L402 endpoint: // 402 challenge -> pay via callback -> retry with preimage const response = await manager.settleViaL402(best, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: "Hello", target_lang: "es" }), }); console.log(response.status, await response.text()); ``` ```csharp using LightningEnable.AgentSdk.Agent; await using var manager = new AgentManager(new AgentManagerOptions { PrivateKey = "", RelayUrls = new List { "wss://agents.lightningenable.com" }, }); await manager.ConnectAsync(); // Discover capabilities (kind 38400) var capabilities = await manager.DiscoverAsync(new DiscoverOptions { Category = "translation", Limit = 10, }); var chosen = capabilities[0]; Console.WriteLine($"Using {chosen.DTag} at {chosen.PriceSats} sats"); // Send a service request (kind 38401) var requestId = await manager.RequestServiceAsync( capabilityId: chosen.Id, budgetSats: 100, parameters: new Dictionary { ["target_lang"] = "es" }); // The provider responds with an agreement (kind 38402) carrying an L402 // endpoint. SettleAsync issues the HTTP call — expect a 402 challenge, // pay the invoice with your wallet, then retry with // Authorization: L402 :. var response = await manager.SettleAsync(agreement); ``` :::note The .NET SDK (0.3.2) does not auto-pay L402 challenges — `SettleAsync` performs the HTTP call and returns the 402 challenge for you to pay and retry. The Python and TypeScript SDKs auto-pay via the pay-invoice callback. For .NET auto-payment against arbitrary L402 endpoints, see [L402-Requests (.NET)](/tools/l402-dotnet). ::: ## After Settlement: Attest Close the loop by publishing a signed 1–5 rating (kind 38403) so other agents can evaluate the provider: ```python await manager.publish_attestation( subject_pubkey=best.pubkey, agreement_id=agreement_event_id, rating=5, content="Fast, accurate translation.", ) score = await manager.get_reputation_score(best.pubkey) # avg 1.0–5.0 or None ``` ```typescript await manager.publishAttestation( best.pubkey, agreementEventId, 5, "Fast, accurate translation." ); const rep = await manager.getReputation(best.pubkey); console.log(rep.average, rep.count); ``` ```csharp await manager.PublishAttestationAsync( subjectPubkey: chosen.Pubkey, agreementId: agreementEventId, rating: 5, content: "Fast, accurate translation."); var reputation = await manager.GetReputationAsync(chosen.Pubkey); ``` ## Next Steps - [ASA Overview](./overview.md) — protocol, event kinds, relay, reputation model - [L402 Producer API](/products/agentic-commerce/l402-producer-api) — the endpoints behind `create_challenge` / `verify_payment` - [HTTP Client Libraries](/tools/l402-requests) — auto-paying L402 clients for arbitrary paid APIs ============================================================================== # AutoGen Integration Source: https://docs.lightningenable.com/tools/autogen ============================================================================== # AutoGen (AG2) Integration Add Lightning payment capability to AutoGen multi-agent conversations. Agents can access L402-protected APIs with automatic micropayments. ## Install ```bash pip install l402-requests[autogen] ``` Set your wallet: ```bash export STRIKE_API_KEY="your-strike-api-key" ``` ## Quick Start ```python from autogen import AssistantAgent, UserProxyAgent from l402_requests.integrations.autogen import register_l402_tools, configure_client from l402_requests import L402Client, BudgetController # Optional: configure budget configure_client(L402Client( budget=BudgetController(max_sats_per_request=500), )) # Agents assistant = AssistantAgent( name="L402Assistant", system_message=( "You can access L402-protected APIs using l402_get and l402_post. " "These tools automatically pay Lightning invoices when needed. " "Check l402_spending_summary to monitor costs. " "Reply TERMINATE when done." ), llm_config={"model": "gpt-4o"}, ) user_proxy = UserProxyAgent( name="UserProxy", human_input_mode="NEVER", is_termination_msg=lambda msg: "TERMINATE" in (msg.get("content") or ""), code_execution_config=False, ) # Register tools register_l402_tools(caller=assistant, executor=user_proxy) # Run user_proxy.initiate_chat( assistant, message="Get the weather forecast for NYC from agent-commerce.store", ) ``` ## Functions ### `register_l402_tools(caller, executor)` Registers all three tools with an AutoGen caller/executor pair: - **`l402_get(url)`** — GET with automatic L402 payment - **`l402_post(url, body)`** — POST with automatic L402 payment - **`l402_spending_summary()`** — Session spending report ### `configure_client(client)` Set a custom `L402Client` before starting the conversation: ```python from l402_requests import L402Client, BudgetController from l402_requests.integrations.autogen import configure_client configure_client(L402Client( budget=BudgetController( max_sats_per_request=1000, max_sats_per_hour=10000, allowed_domains={"agent-commerce.store"}, ), )) ``` ## Wallet Options | Priority | Wallet | Environment Variable | |----------|--------|---------------------| | 1 | LND | `LND_REST_HOST` + `LND_MACAROON_HEX` | | 2 | NWC | `NWC_CONNECTION_STRING` | | 3 | Strike | `STRIKE_API_KEY` | | 4 | OpenNode | `OPENNODE_API_KEY` | ============================================================================== # CrewAI Integration Source: https://docs.lightningenable.com/tools/crewai ============================================================================== # CrewAI Integration Add Lightning payment capability to any CrewAI crew. Agents can access L402-protected APIs with automatic micropayments. ## Install ```bash pip install l402-requests[crewai] ``` Set your wallet: ```bash export STRIKE_API_KEY="your-strike-api-key" ``` ## Quick Start ```python from crewai import Agent, Crew, Task from l402_requests import L402Client, BudgetController from l402_requests.integrations.crewai import L402GetTool, L402PostTool, L402SpendingTool # Shared client for the whole crew client = L402Client( budget=BudgetController(max_sats_per_request=500, max_sats_per_hour=5000), ) # Tools get_tool = L402GetTool(client=client) post_tool = L402PostTool(client=client) spending_tool = L402SpendingTool(client=client) # Agent researcher = Agent( role="API Researcher", goal="Fetch data from L402-gated APIs and summarize findings.", backstory="Expert at accessing Lightning-gated APIs.", tools=[get_tool, spending_tool], ) # Task task = Task( description="Get SEC filing data for Tesla from agent-commerce.store/api/edgar/company/TSLA", expected_output="Summary of Tesla's recent SEC filings.", agent=researcher, ) # Run crew = Crew(agents=[researcher], tasks=[task]) result = crew.kickoff() ``` ## Tools ### L402GetTool HTTP GET with automatic L402 payment. - **Input:** `url` (str) - **Returns:** Response body as text ### L402PostTool HTTP POST with automatic L402 payment. - **Input:** `url` (str), `body` (optional JSON string) - **Returns:** Response body as text ### L402SpendingTool Session spending summary. - **Input:** None - **Returns:** JSON with total sats, per-domain breakdown ## Shared Client Always pass the same `L402Client` to all tools. This ensures: - **Credential cache** is shared (no double-payments) - **Budget** is enforced across all tools as one limit - **Spending log** is unified ============================================================================== # L402-Requests (.NET) Source: https://docs.lightningenable.com/tools/l402-dotnet ============================================================================== # L402-Requests (.NET) **Three lines of C#. Paid APIs just work.** ```csharp using L402Requests; using var client = new L402HttpClient(); var response = await client.GetAsync("https://api.example.com/paid-resource"); Console.WriteLine(await response.Content.ReadAsStringAsync()); ``` That's the entire integration. No payment logic. No invoice parsing. No retry code. No protocol knowledge required. Behind the scenes, `L402Requests` detects the 402 challenge, pays the Lightning invoice from your wallet, caches the credential, and retries the request. You get back a normal `HttpResponseMessage`. The API just worked — and it got paid. ## Install ```bash dotnet add package L402Requests ``` Set one environment variable for your wallet and you're done: ```bash export STRIKE_API_KEY="your-strike-api-key" ``` That's it. Every L402-protected API you call will automatically get paid. ## How It Works You never see any of this — it happens automatically: ``` Your Code L402Requests L402 Server Lightning ──────── ──────────── ─────────── ───────── │ │ │ │ │── GetAsync("/data") ────▶│ │ │ │ │──── GET /data ──────────▶│ │ │ │◀── 402 + invoice + mac ──│ │ │ │ │ │ │ │ check budget │ │ │ │ extract amount │ │ │ │ │ │ │ │──── pay invoice ────────────────────────────── ▶│ │ │◀─── preimage ───────────────────────────────── │ │ │ │ │ │ │── GET /data ────────────▶│ │ │ │ Authorization: L402 │ │ │ │◀──── 200 + data ────────│ │ │◀── 200 + data ──────────│ │ │ ``` 1. You make an HTTP request — `client.GetAsync(url)` 2. If the server returns **200**, the response comes back as-is 3. If the server returns **402** with an L402 challenge: - The invoice is parsed automatically - The amount is checked against your budget - The invoice is paid via your Lightning wallet - The request is retried with `Authorization: L402 {macaroon}:{preimage}` 4. Credentials are cached — subsequent requests to the same endpoint don't re-pay ## Wallet Configuration Set environment variables for your wallet. The library auto-detects in priority order: | Priority | Wallet | Environment Variables | Preimage | Notes | |----------|--------|-----------------------|----------|-------| | 1 | LND | `LND_REST_HOST` + `LND_MACAROON_HEX` | Yes | Requires running a node | | 2 | NWC | `NWC_CONNECTION_STRING` | Yes | CoinOS, CLINK compatible | | 3 | Strike | `STRIKE_API_KEY` | Yes | No infrastructure required | | 4 | OpenNode | `OPENNODE_API_KEY` | Limited | No preimage support | :::tip Recommended: Strike Strike has full preimage support and requires no infrastructure. Set `STRIKE_API_KEY` and you're done. ::: :::note Credential resolution & priority override If an environment variable is empty or a `${...}` placeholder, the client falls back to `~/.lightning-enable/config.json` (shared with the Lightning Enable MCP server), and a `wallets.priority` key in that file can reorder detection. To switch wallets, clear the old credential in **both** places — otherwise the config-file fallback silently re-enables it. ::: ### Strike ```bash export STRIKE_API_KEY="your-strike-api-key" ``` ### LND ```bash export LND_REST_HOST="https://localhost:8080" export LND_MACAROON_HEX="your-admin-macaroon-hex" export LND_TLS_CERT_PATH="/path/to/tls.cert" # optional ``` ### NWC (Nostr Wallet Connect) ```bash export NWC_CONNECTION_STRING="nostr+walletconnect://pubkey?relay=wss://relay&secret=hex" ``` ### OpenNode ```bash export OPENNODE_API_KEY="your-opennode-key" ``` :::warning OpenNode L402 Limitation OpenNode does not return payment preimages, which means L402 credential construction will fail. For L402 use cases, use Strike, LND, or a compatible NWC wallet. ::: ### L402 Wallet Compatibility L402 requires the payment **preimage** (proof of payment) to construct credentials. Not all wallets return it. If yours doesn't, payment succeeds but API access fails. | Wallet | Returns Preimage | L402 Works | Notes | |--------|-----------------|------------|-------| | **LND** | Yes | Yes | Requires running a node | | **NWC (CoinOS)** | Yes | Yes | Free, easy setup | | **NWC (CLINK)** | Yes | Yes | Nostr users | | **Strike** | Yes | Yes | Easy setup, no infrastructure | | **Alby Hub** | Yes | Yes | Run your own hub, NWC compatible | | **Primal** | No | No | Direct payments only | | **OpenNode** | No | No | Direct payments only | For detailed wallet setup instructions, see the [MCP Wallet Setup Guide](/products/agentic-commerce/mcp-wallet-setup). ### Explicit Wallet You can also pass a wallet directly instead of relying on auto-detection: ```csharp using L402Requests; using L402Requests.Wallets; using var client = new L402HttpClient(new StrikeWallet("your-api-key")); var response = await client.GetAsync("https://api.example.com/paid-resource"); ``` ## Budget Controls Safety is built in. Budgets are enabled by default so you can't accidentally overspend: ```csharp using var client = new L402HttpClient(new L402Options { MaxSatsPerRequest = 500, // Max per single payment (default: 1,000) MaxSatsPerHour = 5000, // Hourly rolling limit (default: 10,000) MaxSatsPerDay = 25000, // Daily rolling limit (default: 50,000) AllowedDomains = ["api.example.com"], // Optional domain allowlist }); ``` If a payment would exceed any limit, `BudgetExceededException` is raised **before** the payment is attempted — no sats leave your wallet. To disable budgets entirely: ```csharp using var client = new L402HttpClient(new L402Options { BudgetEnabled = false }); // Not recommended ``` ### Default Limits | Limit | Default | Description | |-------|---------|-------------| | `MaxSatsPerRequest` | 1,000 sats | Rejects any single invoice above this | | `MaxSatsPerHour` | 10,000 sats | Rolling 1-hour window | | `MaxSatsPerDay` | 50,000 sats | Rolling 24-hour window | ### Domain Allowlist Restrict payments to specific domains: ```csharp using var client = new L402HttpClient(new L402Options { AllowedDomains = ["api.example.com", "store.lightningenable.com"], }); ``` Any request to a domain not in the list will raise `DomainNotAllowedException` before attempting payment. ## DI / HttpClientFactory For ASP.NET Core and services using dependency injection: ```csharp // In Program.cs builder.Services.AddL402HttpClient("myapi", options => { options.MaxSatsPerRequest = 500; options.MaxSatsPerHour = 5000; options.AllowedDomains = ["api.example.com"]; }); // In consuming class public class MyService(IHttpClientFactory factory) { public async Task GetPaidData() { var client = factory.CreateClient("myapi"); var response = await client.GetAsync("https://api.example.com/paid-resource"); return await response.Content.ReadAsStringAsync(); } } ``` ## Spending Introspection Track every payment made during a session: ```csharp using var client = new L402HttpClient(); await client.GetAsync("https://api.example.com/data"); await client.GetAsync("https://api.example.com/more-data"); // Inspect spending Console.WriteLine($"Total: {client.SpendingLog.TotalSpent()} sats"); Console.WriteLine($"Last hour: {client.SpendingLog.SpentLastHour()} sats"); Console.WriteLine($"Today: {client.SpendingLog.SpentToday()} sats"); Console.WriteLine($"By domain: {string.Join(", ", client.SpendingLog.ByDomain())}"); // Export as JSON for auditing Console.WriteLine(client.SpendingLog.ToJson()); ``` ## Credential Caching L402 credentials are cached by `(domain, path_prefix)` so you don't re-pay for the same endpoint within a session. The cache uses an LRU eviction strategy with a default TTL of 1 hour. ```csharp using var client = new L402HttpClient(new L402Options { CacheMaxSize = 256, // Maximum cached credentials CacheTtlSeconds = 3600.0, // 1 hour TTL }); ``` ## Error Handling ```csharp using L402Requests; using var client = new L402HttpClient(); try { var response = await client.GetAsync("https://api.example.com/paid-resource"); } catch (BudgetExceededException e) { Console.WriteLine($"Over budget: {e.LimitType} limit is {e.LimitSats} sats"); } catch (PaymentFailedException e) { Console.WriteLine($"Payment failed: {e.Reason}"); } catch (NoWalletException) { Console.WriteLine("No wallet configured — set STRIKE_API_KEY or other wallet env vars"); } ``` | Exception | When | |-----------|------| | `BudgetExceededException` | Payment would exceed a budget limit | | `PaymentFailedException` | Lightning payment failed (routing, timeout, etc.) | | `InvoiceExpiredException` | Invoice expired before payment | | `NoWalletException` | No wallet env vars detected | | `DomainNotAllowedException` | Domain not in `AllowedDomains` | | `ChallengeParseException` | Malformed L402 challenge header | ## Example: Lightning Enable Store Access the [Lightning Enable Store](https://store.lightningenable.com) — a live L402 commerce demo. :::warning Budget Configuration Required Store products cost **25,000 - 45,000+ sats** (including shipping). The default budget limit of 1,000 sats per request will reject these payments. You must increase `MaxSatsPerRequest` before purchasing. ::: :::info US Shipping Only The Lightning Enable Store currently ships to **US addresses only**. ::: The store uses a **two-step L402 flow** designed for physical goods commerce: ```csharp using System.Net.Http.Json; using L402Requests; using L402Requests.Wallets; // Step 1: Browse catalog (free, no payment) using var client = new L402HttpClient(new L402Options { MaxSatsPerRequest = 50000, }); var catalog = await client.GetAsync("https://store.lightningenable.com/api/store/catalog"); Console.WriteLine(await catalog.Content.ReadAsStringAsync()); // Step 2: Checkout WITHOUT auto-pay — capture the 402 challenge yourself, // because the separate /claim call needs the macaroon from this exact challenge using var plainHttp = new HttpClient(); var checkoutContent = JsonContent.Create(new { items = new[] { new { productId = 2, quantity = 1, size = "L", color = "Black" } } }); var checkout = await plainHttp.PostAsync( "https://store.lightningenable.com/api/store/checkout", checkoutContent); if ((int)checkout.StatusCode != 402) throw new InvalidOperationException($"Expected 402, got {(int)checkout.StatusCode}"); var challenge = L402Challenge.TryParse(checkout) ?? throw new InvalidOperationException("No L402 challenge in response"); // Step 3: Pay the invoice with your configured wallet — returns the preimage var wallet = WalletDetector.DetectWallet(); var preimage = await wallet.PayInvoiceAsync(challenge.Invoice); // Step 4: Claim the order (header-only — body can be empty) using var claimRequest = new HttpRequestMessage( HttpMethod.Post, "https://store.lightningenable.com/api/store/claim") { Content = new StringContent("{}", System.Text.Encoding.UTF8, "application/json") }; claimRequest.Headers.TryAddWithoutValidation( "Authorization", $"L402 {challenge.Macaroon}:{preimage}"); var claim = await plainHttp.SendAsync(claimRequest); Console.WriteLine(await claim.Content.ReadAsStringAsync()); // contains claimUrl ``` :::note Why capture the 402 manually? Auto-paid requests don't currently expose the challenge macaroon afterward (`PaymentRecord` has no macaroon field yet), and paying the wallet directly bypasses the client's budget checks — validate the invoice amount before paying. The next minor release adds the macaroon to the spending-log record, after which the auto-pay flow can claim directly. ::: ## API Reference ### `L402HttpClient` ```csharp new L402HttpClient() // Auto-detect wallet, default options new L402HttpClient(IWallet wallet) // Explicit wallet new L402HttpClient(L402Options options) // Custom options new L402HttpClient(IWallet? wallet, L402Options? options) // Both ``` Methods: `.GetAsync()`, `.PostAsync()`, `.PutAsync()`, `.PatchAsync()`, `.DeleteAsync()`, `.SendAsync()` Properties: - `.SpendingLog` — `SpendingLog` instance for payment history ### `L402Options` ```csharp new L402Options { MaxSatsPerRequest = 1000, // Default: 1000 MaxSatsPerHour = 10000, // Default: 10000 MaxSatsPerDay = 50000, // Default: 50000 AllowedDomains = null, // null = all domains Wallet = null, // null = auto-detect BudgetEnabled = true, // false to disable all limits CacheMaxSize = 256, // LRU cache size CacheTtlSeconds = 3600.0, // null to disable expiration } ``` ### `ServiceCollectionExtensions` ```csharp services.AddL402HttpClient("name", options => { ... }); services.AddL402HttpClient(options => { ... }); ``` ### Wallet Classes - `StrikeWallet(string apiKey)` - `LndWallet(string host, string macaroonHex, string? tlsCertPath = null)` - `NwcWallet(string connectionString, TimeSpan? timeout = null)` - `OpenNodeWallet(string apiKey)` ## Also Available - **Python**: [`l402-requests`](/tools/l402-requests) — same "three lines of code" experience for Python - **TypeScript**: [`l402-requests`](/tools/l402-ts) — same "three lines of code" experience for TypeScript/Node.js ## Source Code [GitHub Repository](https://github.com/refined-element/l402-dotnet) (MIT License) ============================================================================== # L402-Requests (Python) Source: https://docs.lightningenable.com/tools/l402-requests ============================================================================== # L402-Requests **Three lines of Python. Paid APIs just work.** ```python import l402_requests response = l402_requests.get("https://api.example.com/paid-resource") print(response.json()) ``` That's the entire integration. No payment logic. No invoice parsing. No retry code. No protocol knowledge required. Behind the scenes, `L402-Requests` detects the 402 challenge, pays the Lightning invoice from your wallet, caches the credential, and retries the request. You get back a normal `httpx.Response`. The API just worked — and it got paid. ## Install ```bash pip install l402-requests ``` Set one environment variable for your wallet and you're done: ```bash export STRIKE_API_KEY="your-strike-api-key" ``` That's it. Every L402-protected API you call will automatically get paid. ## How It Works You never see any of this — it happens automatically: ``` Your Code L402-Requests L402 Server Lightning ──────── ───────────── ─────────── ───────── │ │ │ │ │──── GET /resource ──────▶│ │ │ │ │──── GET /resource ──────▶│ │ │ │◀── 402 + invoice + mac ──│ │ │ │ │ │ │ │ check budget │ │ │ │ extract amount │ │ │ │ │ │ │ │──── pay invoice ────────────────────────────── ▶│ │ │◀─── preimage ───────────────────────────────── │ │ │ │ │ │ │── GET /resource ────────▶│ │ │ │ Authorization: L402 │ │ │ │◀──── 200 + data ────────│ │ │◀── 200 + data ──────────│ │ │ ``` 1. You make an HTTP request — `l402_requests.get(url)` 2. If the server returns **200**, the response comes back as-is 3. If the server returns **402** with an L402 challenge: - The invoice is parsed automatically - The amount is checked against your budget - The invoice is paid via your Lightning wallet - The request is retried with `Authorization: L402 {macaroon}:{preimage}` 4. Credentials are cached — subsequent requests to the same endpoint don't re-pay ## Wallet Configuration Set environment variables for your wallet. The library auto-detects in priority order: | Priority | Wallet | Environment Variables | Preimage | Notes | |----------|--------|-----------------------|----------|-------| | 1 | LND | `LND_REST_HOST` + `LND_MACAROON_HEX` | Yes | Requires running a node | | 2 | NWC | `NWC_CONNECTION_STRING` | Yes | CoinOS, CLINK compatible | | 3 | Strike | `STRIKE_API_KEY` | Yes | No infrastructure required | | 4 | OpenNode | `OPENNODE_API_KEY` | Limited | No preimage support | :::tip Recommended: Strike Strike has full preimage support and requires no infrastructure. Set `STRIKE_API_KEY` and you're done. ::: :::note Credential resolution & priority override If an environment variable is empty or a `${...}` placeholder, the client falls back to `~/.lightning-enable/config.json` (shared with the Lightning Enable MCP server), and a `wallets.priority` key in that file can reorder detection. To switch wallets, clear the old credential in **both** places — otherwise the config-file fallback silently re-enables it. ::: ### Strike ```bash export STRIKE_API_KEY="your-strike-api-key" ``` ### LND ```bash export LND_REST_HOST="https://localhost:8080" export LND_MACAROON_HEX="your-admin-macaroon-hex" export LND_TLS_CERT_PATH="/path/to/tls.cert" # optional ``` ### NWC (Nostr Wallet Connect) NWC support ships in the base install (since 0.3.0 — the old `[nwc]` extra is an empty no-op kept for compatibility): ```bash export NWC_CONNECTION_STRING="nostr+walletconnect://pubkey?relay=wss://relay&secret=hex" ``` ### OpenNode ```bash export OPENNODE_API_KEY="your-opennode-key" ``` :::warning OpenNode L402 Limitation OpenNode does not return payment preimages, which means L402 credential construction will fail. For L402 use cases, use Strike, LND, or a compatible NWC wallet. ::: ### L402 Wallet Compatibility L402 requires the payment **preimage** (proof of payment) to construct credentials. Not all wallets return it. If yours doesn't, payment succeeds but API access fails. | Wallet | Returns Preimage | L402 Works | Notes | |--------|-----------------|------------|-------| | **LND** | ✅ Always | ✅ Yes | Requires running a node | | **NWC (CoinOS)** | ✅ Yes | ✅ Yes | Free, easy setup | | **NWC (CLINK)** | ✅ Yes | ✅ Yes | Nostr users | | **Strike** | ✅ Yes | ✅ Yes | Easy setup, no infrastructure | | **Alby Hub** | ✅ Yes | ✅ Yes | Run your own hub, NWC compatible | | **Primal** | ❌ No | ❌ No | Direct payments only | | **OpenNode** | ❌ No | ❌ No | Direct payments only | For detailed wallet setup instructions, see the [MCP Wallet Setup Guide](/products/agentic-commerce/mcp-wallet-setup). ### Explicit Wallet You can also pass a wallet directly instead of relying on auto-detection: ```python from l402_requests import L402Client, StrikeWallet client = L402Client( wallet=StrikeWallet(api_key="your-key"), ) response = client.get("https://api.example.com/paid-resource") ``` ## Budget Controls Safety is built in. Budgets are enabled by default so you can't accidentally overspend: ```python from l402_requests import L402Client, BudgetController client = L402Client( budget=BudgetController( max_sats_per_request=500, # Max per single payment (default: 1,000) max_sats_per_hour=5000, # Hourly rolling limit (default: 10,000) max_sats_per_day=25000, # Daily rolling limit (default: 50,000) allowed_domains={"api.example.com"}, # Optional domain allowlist ) ) ``` If a payment would exceed any limit, `BudgetExceededError` is raised **before** the payment is attempted — no sats leave your wallet. To disable budgets entirely: ```python client = L402Client(budget=None) # Not recommended ``` ### Default Limits | Limit | Default | Description | |-------|---------|-------------| | `max_sats_per_request` | 1,000 sats | Rejects any single invoice above this | | `max_sats_per_hour` | 10,000 sats | Rolling 1-hour window | | `max_sats_per_day` | 50,000 sats | Rolling 24-hour window | ### Domain Allowlist Restrict payments to specific domains: ```python budget = BudgetController( allowed_domains={"api.example.com", "store.lightningenable.com"} ) ``` Any request to a domain not in the list will raise `DomainNotAllowedError` before attempting payment. ## Async Support Full async support via `AsyncL402Client`: ```python from l402_requests import AsyncL402Client async with AsyncL402Client() as client: response = await client.get("https://api.example.com/paid-resource") print(response.json()) ``` ## Spending Introspection Track every payment made during a session: ```python from l402_requests import L402Client client = L402Client() client.get("https://api.example.com/data") client.get("https://api.example.com/more-data") # Inspect spending print(f"Total: {client.spending_log.total_spent()} sats") print(f"Last hour: {client.spending_log.spent_last_hour()} sats") print(f"Today: {client.spending_log.spent_today()} sats") print(f"By domain: {client.spending_log.by_domain()}") # Export as JSON for auditing print(client.spending_log.to_json()) ``` ## Credential Caching L402 credentials are cached by `(domain, path_prefix)` so you don't re-pay for the same endpoint within a session. The cache uses an LRU eviction strategy with a default TTL of 1 hour. ```python from l402_requests import L402Client, CredentialCache client = L402Client( credential_cache=CredentialCache( max_size=256, # Maximum cached credentials default_ttl=3600.0, # 1 hour TTL ) ) ``` ## Error Handling ```python from l402_requests import L402Client, BudgetExceededError, PaymentFailedError, NoWalletError client = L402Client() try: response = client.get("https://api.example.com/paid-resource") except BudgetExceededError as e: print(f"Over budget: {e.limit_type} limit is {e.limit_sats} sats") except PaymentFailedError as e: print(f"Payment failed: {e.reason}") except NoWalletError: print("No wallet configured — set STRIKE_API_KEY or other wallet env vars") ``` | Exception | When | |-----------|------| | `BudgetExceededError` | Payment would exceed a budget limit | | `PaymentFailedError` | Lightning payment failed (routing, timeout, etc.) | | `InvoiceExpiredError` | Invoice expired before payment | | `NoWalletError` | No wallet env vars detected | | `DomainNotAllowedError` | Domain not in `allowed_domains` | | `ChallengeParseError` | Malformed L402 challenge header | ## Example: Lightning Enable Store Access the [Lightning Enable Store](https://store.lightningenable.com) — a live L402 commerce demo. :::warning Budget Configuration Required Store products cost **25,000 - 45,000+ sats** (including shipping). The default budget limit of 1,000 sats per request will reject these payments. You must increase `max_sats_per_request` before purchasing. ::: :::info US Shipping Only The Lightning Enable Store currently ships to **US addresses only**. ::: The store uses a **two-step L402 flow** designed for physical goods commerce. Checkout creates the order and returns a 402 with an invoice. After payment, you claim the order at a separate `/claim` endpoint with the L402 credential. This is intentional — it separates payment from fulfillment, and the claim URL can be shared with a gift recipient to enter their own shipping address. ```python import json import httpx import httpx from l402_requests import L402Client, BudgetController from l402_requests.challenge import parse_challenge from l402_requests.wallets import auto_detect_wallet # Step 1: Browse catalog (free, no payment) client = L402Client( budget=BudgetController(max_sats_per_request=50000), ) catalog = client.get("https://store.lightningenable.com/api/store/catalog") for product in catalog.json()["products"]: print(f"[{product['id']}] {product['name']} — {product['priceSats']} sats") # Step 2: Checkout WITHOUT auto-pay — capture the 402 challenge yourself, # because the separate /claim call needs the macaroon from this exact challenge checkout = httpx.post( "https://store.lightningenable.com/api/store/checkout", json={"items": [{"productId": 2, "quantity": 1, "size": "L", "color": "Black"}]}, ) assert checkout.status_code == 402 challenge = parse_challenge(checkout.headers["www-authenticate"]) # Step 3: Pay the invoice with your configured wallet — returns the preimage wallet = auto_detect_wallet() preimage = wallet.pay_invoice_sync(challenge.invoice) # Step 4: Claim the order (header-only — body can be empty) claim = httpx.post( "https://store.lightningenable.com/api/store/claim", headers={ "Authorization": f"L402 {challenge.macaroon}:{preimage}", "Content-Type": "application/json", }, content="{}", ) print(f"Claim URL: {claim.json()['claimUrl']}") # Step 5: Share the claim URL — recipient enters shipping address there ``` :::note Why capture the 402 manually? Auto-paid requests (`client.post(...)`) don't currently expose the challenge macaroon afterward, and paying the wallet directly bypasses the client's budget checks — so for two-step purchase flows, capture the challenge first as shown and check the amount before paying. The next minor release adds `macaroon` to the spending-log `PaymentRecord`, after which the auto-pay flow can claim directly. ::: ### Store claim details - The `/claim` endpoint accepts the L402 credential in the **`Authorization` header only** — the request body can be empty (`{}`). - After claiming, share the `claimUrl` with the recipient to enter their shipping address. - Claim tokens expire in 7 days. ## API Reference ### `L402Client` ```python L402Client( wallet: WalletBase | None = None, budget: BudgetController | None = , credential_cache: CredentialCache | None = None, **httpx_kwargs, ) ``` Methods: `.get()`, `.post()`, `.put()`, `.delete()`, `.patch()`, `.head()`, `.options()`, `.request()` Properties: - `.spending_log` — `SpendingLog` instance for payment history ### `AsyncL402Client` Same API as `L402Client` but all methods are `async`. Use as async context manager: ```python async with AsyncL402Client() as client: response = await client.get(url) ``` ### `BudgetController` ```python BudgetController( max_sats_per_request: int = 1000, max_sats_per_hour: int = 10000, max_sats_per_day: int = 50000, allowed_domains: set[str] | None = None, ) ``` ### Wallet Classes - `StrikeWallet(api_key: str)` - `LndWallet(host: str, macaroon_hex: str, tls_cert_path: str | None = None)` - `NwcWallet(connection_string: str, timeout: float = 30.0)` - `OpenNodeWallet(api_key: str)` ## Also Available - **TypeScript**: [`l402-requests`](/tools/l402-ts) — same "three lines of code" experience for TypeScript/Node.js - **.NET**: [`L402Requests`](/tools/l402-dotnet) — same "three lines of code" experience for .NET ## Source Code [GitHub Repository](https://github.com/refined-element/l402-requests) (MIT License) ============================================================================== # L402-Requests (TypeScript) Source: https://docs.lightningenable.com/tools/l402-ts ============================================================================== # L402-Requests (TypeScript) **Three lines of TypeScript. Paid APIs just work.** ```typescript import { get } from 'l402-requests'; const response = await get("https://api.example.com/paid-resource"); console.log(await response.json()); ``` That's the entire integration. No payment logic. No invoice parsing. No retry code. No protocol knowledge required. Behind the scenes, `l402-requests` detects the 402 challenge, pays the Lightning invoice from your wallet, caches the credential, and retries the request. You get back a normal `Response`. The API just worked — and it got paid. ## Install ```bash npm install l402-requests ``` Set one environment variable for your wallet and you're done: ```bash export STRIKE_API_KEY="your-strike-api-key" ``` That's it. Every L402-protected API you call will automatically get paid. ## How It Works You never see any of this — it happens automatically: ``` Your Code l402-requests L402 Server Lightning ───────── ───────────── ─────────── ───────── │ │ │ │ │── GET /resource ──▶│ │ │ │ │── GET /resource ───────▶│ │ │ │◀── 402 + invoice + mac ─│ │ │ │ │ │ │ │ check budget │ │ │ │ extract amount │ │ │ │ │ │ │ │── pay invoice ──────────────────────────────▶│ │ │◀── preimage ────────────────────────────────│ │ │ │ │ │ │── GET /resource ────────▶│ │ │ │ Authorization: L402 │ │ │ │◀──── 200 + data ────────│ │ │◀── 200 + data ─────│ │ │ ``` 1. You make an HTTP request — `get(url)` 2. If the server returns **200**, the response comes back as-is 3. If the server returns **402** with an L402 challenge: - The invoice is parsed automatically - The amount is checked against your budget - The invoice is paid via your Lightning wallet - The request is retried with `Authorization: L402 {macaroon}:{preimage}` 4. Credentials are cached — subsequent requests to the same endpoint don't re-pay ## Wallet Configuration Set environment variables for your wallet. The library auto-detects in priority order: | Priority | Wallet | Environment Variables | Preimage | Notes | |----------|--------|-----------------------|----------|-------| | 1 | LND | `LND_REST_HOST` + `LND_MACAROON_HEX` | Yes | Requires running a node | | 2 | NWC | `NWC_CONNECTION_STRING` | Yes | CoinOS, CLINK compatible | | 3 | Strike | `STRIKE_API_KEY` | Yes | No infrastructure required | | 4 | OpenNode | `OPENNODE_API_KEY` | Limited | No preimage support | :::tip Recommended: Strike Strike has full preimage support and requires no infrastructure. Set `STRIKE_API_KEY` and you're done. ::: :::note Credential resolution & priority override If an environment variable is empty or a `${...}` placeholder, the client falls back to `~/.lightning-enable/config.json` (shared with the Lightning Enable MCP server), and a `wallets.priority` key in that file can reorder detection. To switch wallets, clear the old credential in **both** places — otherwise the config-file fallback silently re-enables it. ::: ### Strike ```bash export STRIKE_API_KEY="your-strike-api-key" ``` ### LND ```bash export LND_REST_HOST="https://localhost:8080" export LND_MACAROON_HEX="your-admin-macaroon-hex" ``` ### NWC (Nostr Wallet Connect) NWC requires optional peer dependencies: ```bash npm install @noble/secp256k1 ws export NWC_CONNECTION_STRING="nostr+walletconnect://pubkey?relay=wss://relay&secret=hex" ``` ### OpenNode ```bash export OPENNODE_API_KEY="your-opennode-key" ``` :::warning OpenNode L402 Limitation OpenNode does not return payment preimages, which means L402 credential construction will fail. For L402 use cases, use Strike, LND, or a compatible NWC wallet. ::: ### L402 Wallet Compatibility L402 requires the payment **preimage** (proof of payment) to construct credentials. Not all wallets return it. If yours doesn't, payment succeeds but API access fails. | Wallet | Returns Preimage | L402 Works | Notes | |--------|-----------------|------------|-------| | **LND** | Yes | Yes | Requires running a node | | **NWC (CoinOS)** | Yes | Yes | Free, easy setup | | **NWC (CLINK)** | Yes | Yes | Nostr users | | **Strike** | Yes | Yes | Easy setup, no infrastructure | | **Alby Hub** | Yes | Yes | Run your own hub, NWC compatible | | **Primal** | No | No | Direct payments only | | **OpenNode** | No | No | Direct payments only | For detailed wallet setup instructions, see the [MCP Wallet Setup Guide](/products/agentic-commerce/mcp-wallet-setup). ### Explicit Wallet You can also pass a wallet directly instead of relying on auto-detection: ```typescript import { L402Client, StrikeWallet } from 'l402-requests'; const client = new L402Client({ wallet: new StrikeWallet("your-key"), }); const response = await client.get("https://api.example.com/paid-resource"); ``` ## Budget Controls Safety is built in. Budgets are enabled by default so you can't accidentally overspend: ```typescript import { L402Client, BudgetController } from 'l402-requests'; const client = new L402Client({ budget: new BudgetController({ maxSatsPerRequest: 500, // Max per single payment (default: 1,000) maxSatsPerHour: 5000, // Hourly rolling limit (default: 10,000) maxSatsPerDay: 25000, // Daily rolling limit (default: 50,000) allowedDomains: new Set(["api.example.com"]), }), }); ``` If a payment would exceed any limit, `BudgetExceededError` is thrown **before** the payment is attempted — no sats leave your wallet. To disable budgets entirely: ```typescript const client = new L402Client({ budget: null }); // Not recommended ``` ### Default Limits | Limit | Default | Description | |-------|---------|-------------| | `maxSatsPerRequest` | 1,000 sats | Rejects any single invoice above this | | `maxSatsPerHour` | 10,000 sats | Rolling 1-hour window | | `maxSatsPerDay` | 50,000 sats | Rolling 24-hour window | ### Domain Allowlist Restrict payments to specific domains: ```typescript const budget = new BudgetController({ allowedDomains: new Set(["api.example.com", "store.lightningenable.com"]), }); ``` Any request to a domain not in the list will raise `DomainNotAllowedError` before attempting payment. ## Spending Introspection Track every payment made during a session: ```typescript import { L402Client } from 'l402-requests'; const client = new L402Client(); await client.get("https://api.example.com/data"); await client.get("https://api.example.com/more-data"); // Inspect spending console.log(`Total: ${client.spendingLog.totalSpent()} sats`); console.log(`Last hour: ${client.spendingLog.spentLastHour()} sats`); console.log(`Today: ${client.spendingLog.spentToday()} sats`); console.log(`By domain:`, client.spendingLog.byDomain()); // Export as JSON for auditing console.log(client.spendingLog.toJSON()); ``` ## Credential Caching L402 credentials are cached by `(domain, path_prefix)` so you don't re-pay for the same endpoint within a session. The cache uses an LRU eviction strategy with a default TTL of 1 hour. ```typescript import { L402Client, CredentialCache } from 'l402-requests'; const client = new L402Client({ credentialCache: new CredentialCache({ maxSize: 256, // Maximum cached credentials defaultTtlMs: 3_600_000, // 1 hour TTL }), }); ``` ## Error Handling ```typescript import { L402Client, BudgetExceededError, PaymentFailedError, NoWalletError } from 'l402-requests'; const client = new L402Client(); try { const response = await client.get("https://api.example.com/paid-resource"); } catch (e) { if (e instanceof BudgetExceededError) { console.log(`Over budget: ${e.limitType} limit is ${e.limitSats} sats`); } else if (e instanceof PaymentFailedError) { console.log(`Payment failed: ${e.reason}`); } else if (e instanceof NoWalletError) { console.log("No wallet configured — set STRIKE_API_KEY or other wallet env vars"); } } ``` | Exception | When | |-----------|------| | `BudgetExceededError` | Payment would exceed a budget limit | | `PaymentFailedError` | Lightning payment failed (routing, timeout, etc.) | | `InvoiceExpiredError` | Invoice expired before payment | | `NoWalletError` | No wallet env vars detected | | `DomainNotAllowedError` | Domain not in `allowedDomains` | | `ChallengeParseError` | Malformed L402 challenge header | ## Example: Lightning Enable Store Access the [Lightning Enable Store](https://store.lightningenable.com) — a live L402 commerce demo. :::warning Budget Configuration Required Store products cost **25,000 - 45,000+ sats** (including shipping). The default budget limit of 1,000 sats per request will reject these payments. You must increase `maxSatsPerRequest` before purchasing. ::: ```typescript import { L402Client, BudgetController, parseChallenge, autoDetectWallet, extractAmountSats } from 'l402-requests'; // Step 1: Browse catalog (free, no payment) const client = new L402Client({ budget: new BudgetController({ maxSatsPerRequest: 50000 }), }); const catalog = await client.get("https://store.lightningenable.com/api/store/catalog"); const products = (await catalog.json()).products; for (const p of products) { console.log(`[${p.id}] ${p.name} — ${p.priceSats} sats`); } // Step 2: Checkout WITHOUT auto-pay — capture the 402 challenge yourself, // because the separate /claim call needs the macaroon from this exact challenge const checkout = await fetch("https://store.lightningenable.com/api/store/checkout", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ items: [{ productId: 2, quantity: 1, size: "L", color: "Black" }], }), }); if (checkout.status !== 402) throw new Error(`Expected 402, got ${checkout.status}`); const challenge = parseChallenge(checkout.headers.get("www-authenticate")!); // Step 3: Pay the invoice with your configured wallet — returns the preimage const wallet = await autoDetectWallet(); const preimage = await wallet.payInvoice(challenge.invoice); // Step 4: Claim the order (header-only — body can be empty) const claim = await fetch("https://store.lightningenable.com/api/store/claim", { method: "POST", headers: { "Authorization": `L402 ${challenge.macaroon}:${preimage}`, // macaroon:preimage "Content-Type": "application/json", }, body: "{}", }); const claimData = await claim.json(); console.log(`Claim URL: ${claimData.claimUrl}`); ``` :::note Why capture the 402 manually? Auto-paid requests (`client.post(...)`) don't currently expose the challenge macaroon afterward (`PaymentRecord` has no `macaroon` field yet), and paying the wallet directly bypasses the client's budget checks — check `extractAmountSats(challenge.invoice)` before paying. The next minor release adds `macaroon` to the spending-log record, after which the auto-pay flow can claim directly. ::: ## API Reference ### `L402Client` ```typescript new L402Client({ wallet?: Wallet, budget?: BudgetController | null, // undefined = default, null = disabled credentialCache?: CredentialCache, fetchOptions?: RequestInit, }) ``` Methods: `.get()`, `.post()`, `.put()`, `.delete()`, `.patch()`, `.head()`, `.fetch()` Properties: - `.spendingLog` — `SpendingLog` instance for payment history ### Module-Level Convenience Functions ```typescript import { get, post, put, del, patch } from 'l402-requests'; ``` Uses a lazy singleton `L402Client` with default options. ### `BudgetController` ```typescript new BudgetController({ maxSatsPerRequest?: number, // default: 1000 maxSatsPerHour?: number, // default: 10000 maxSatsPerDay?: number, // default: 50000 allowedDomains?: Set, // default: null (all domains) }) ``` ### Wallet Classes - `StrikeWallet(apiKey: string, baseUrl?: string)` - `LndWallet(host: string, macaroonHex: string)` - `NwcWallet(connectionString: string, timeout?: number)` - `OpenNodeWallet(apiKey: string, baseUrl?: string)` ## Zero Dependencies The core library has **zero required dependencies**. It uses Node.js 18+ built-in `fetch()` for HTTP requests. Only the NWC wallet adapter requires optional peer dependencies (`@noble/secp256k1` and `ws`). ## Also Available - **Python**: [`l402-requests`](/tools/l402-requests) — same "three lines of code" experience for Python - **.NET**: [`L402Requests`](/tools/l402-dotnet) — same experience for .NET ## Source Code [GitHub Repository](https://github.com/refined-element/l402-ts) (MIT License) ============================================================================== # LangChain Integration Source: https://docs.lightningenable.com/tools/langchain ============================================================================== # LangChain Integration Add Lightning payment capability to any LangChain agent. Your agent can access L402-protected APIs with automatic micropayments. ## Install ```bash pip install l402-requests[langchain] langchain-openai langgraph ``` (The `[langchain]` extra brings in `langchain-core` for the tool classes; `langchain-openai` and `langgraph` are needed by the Quick Start below — swap them for your own model provider and agent runtime if different.) Set your wallet: ```bash export STRIKE_API_KEY="your-strike-api-key" ``` ## Quick Start ```python from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent from l402_requests.integrations.langchain import L402FetchTool, L402SpendingTool # Create tools tools = [L402FetchTool(), L402SpendingTool()] # Create agent llm = ChatOpenAI(model="gpt-4o") agent = create_react_agent(llm, tools) # Run result = agent.invoke({ "messages": [("user", "Get the weather forecast for NYC from agent-commerce.store")] }) ``` The agent will call `l402_fetch`, automatically pay the Lightning invoice when it gets a 402, and return structured data. ## Tools ### L402FetchTool HTTP GET or POST with automatic L402 payment handling. ```python from l402_requests import L402Client, BudgetController from l402_requests.integrations.langchain import L402FetchTool # Custom budget client = L402Client( budget=BudgetController( max_sats_per_request=500, max_sats_per_hour=5000, ) ) tool = L402FetchTool(client=client) ``` **Parameters:** - `url` (str) — The full URL to request - `method` (str) — `GET` or `POST` (default: `GET`) - `body` (str, optional) — JSON string body for POST requests ### L402SpendingTool Check how many sats have been spent in this session. ```python from l402_requests.integrations.langchain import L402SpendingTool tool = L402SpendingTool(client=client) # Share the same client ``` ## Shared Client Pass the same `L402Client` to all tools so they share credential cache and budget: ```python from l402_requests import L402Client, BudgetController from l402_requests.integrations.langchain import L402FetchTool, L402SpendingTool client = L402Client( budget=BudgetController(max_sats_per_request=1000), ) tools = [ L402FetchTool(client=client), L402SpendingTool(client=client), ] ``` ## Wallet Options The wallet is auto-detected from environment variables: | Priority | Wallet | Environment Variable | |----------|--------|---------------------| | 1 | LND | `LND_REST_HOST` + `LND_MACAROON_HEX` | | 2 | NWC | `NWC_CONNECTION_STRING` | | 3 | Strike | `STRIKE_API_KEY` | | 4 | OpenNode | `OPENNODE_API_KEY` | **Recommended:** Strike — full L402 support, no infrastructure required. ============================================================================== # Troubleshooting Source: https://docs.lightningenable.com/troubleshooting ============================================================================== # Troubleshooting This guide covers the most common issues merchants encounter when integrating with Lightning Enable, along with step-by-step instructions to diagnose and resolve each one. --- ## 401 Unauthorized A `401` response means the API could not authenticate your request. There are several possible causes. ### Missing or Invalid API Key **Error response:** ```json { "error": "API key required", "message": "Please provide API key in X-API-Key header" } ``` **Troubleshooting steps:** 1. Verify you are sending the `X-API-Key` header with every request: ```bash curl -H "X-API-Key: YOUR_API_KEY" https://api.lightningenable.com/api/merchant/me ``` 2. Confirm the key has no extra whitespace or line breaks. Copy it fresh from the dashboard at `https://api.lightningenable.com/dashboard/settings` (the key is not included in any email). 3. Lightning Enable API keys are environment-agnostic — every request goes to `https://api.lightningenable.com`, and there is no separate dev/prod Lightning Enable key. If you are thinking about development vs. production, that distinction lives in your **payment provider** account (e.g., which OpenNode or Strike key you configured), not in your Lightning Enable API key. 4. If the key was recently regenerated, the old key is immediately invalidated. Update all clients and services to use the new key. **See also:** [Authentication](/api-reference/authentication) ### Expired or Inactive Subscription If your subscription is canceled, past due, or unpaid, the API key remains valid for authentication but subscription enforcement middleware will reject requests. In some cases this surfaces as a `401`; more commonly it returns `403` (see [403 Forbidden](#403-forbidden) below). **Troubleshooting steps:** 1. Log into your Stripe customer portal to check your subscription status. 2. If your payment method has expired, update it and retry. --- ## 402 Payment Required A `402` response is **not an error** -- it is the first step of the L402 payment flow. The server is telling your client that it needs to pay a Lightning invoice before accessing the requested resource. **Example response:** ```http HTTP/1.1 402 Payment Required WWW-Authenticate: L402 macaroon="AgEB...", invoice="lnbc..." { "error": "Payment Required", "message": "Pay the Lightning invoice to access this API", "l402": { "macaroon": "AgEB...", "invoice": "lnbc100n1p...", "amount_sats": 10, "payment_hash": "abc123...", "expires_at": "2026-01-09T13:00:00Z" }, "instructions": { "step1": "Pay the Lightning invoice using any Lightning wallet", "step2": "Copy the preimage (proof of payment) from your wallet", "step3": "Include in request: Authorization: L402 :" } } ``` **What to do:** 1. Pay the Lightning invoice included in the `l402.invoice` field using any Lightning wallet. 2. Obtain the **preimage** (proof of payment) from the wallet after payment succeeds. 3. Retry the original request with the `Authorization` header: ```bash curl https://api.lightningenable.com/l402/proxy/{proxyId}/endpoint \ -H "Authorization: L402 :" ``` 4. The token can be reused for subsequent requests until it expires (default: 1 hour). **If you did not expect a 402:** - You may be hitting an L402-protected proxy endpoint (`/l402/proxy/*`) rather than a standard API endpoint. - Standard API endpoints (`/api/payments`, `/api/refunds`, etc.) never return 402. **See also:** [L402 API](/api-reference/l402), [How It Works](/products/agentic-commerce/how-it-works) --- ## 403 Forbidden A `403` response means your identity is confirmed but you are not authorized to perform the requested action. ### Account Inactive ```json { "error": "Account inactive", "message": "Your account is inactive. Please contact support.", "action_required": "contact_support" } ``` **Resolution:** Contact support@lightningenable.com to reactivate your account. ### Subscription Required ```json { "error": "Subscription required", "message": "Your plan tier requires an active subscription. Please subscribe to continue using the service.", "current_plan": "individual", "action_required": "subscribe" } ``` **Resolution:** Subscribe at [lightningenable.com](https://lightningenable.com). All paid plans require an active Stripe subscription. ### Subscription Not Active (Past Due / Canceled) ```json { "error": "Subscription not active", "message": "Your subscription payment is past due. Please update your payment method to continue using the service.", "subscription_status": "past_due", "action_required": "update_payment_method" } ``` **Possible statuses and actions:** | Status | Action Required | |--------|-----------------| | `past_due` | Update payment method | | `canceled` | Renew subscription | | `unpaid` | Update payment method | | `incomplete` | Complete initial payment | | `incomplete_expired` | Start a new subscription | **Resolution:** Access the Stripe customer portal via `POST /api/stripe/customer-portal` (authenticated with your merchant API key; pass a `returnUrl` in the body). The response contains a `portalUrl` — open it to update your payment method or renew. ### Feature Not Available on Your Plan ```json { "error": "Feature not available", "message": "Refund processing is not enabled for your account. Please contact support.", "feature": "refunds", "current_plan": "free", "required_plan": null, "action_required": "contact_support" } ``` **Plan feature matrix:** | Feature | Free Producer Sandbox ($0) | Agentic Commerce ($49/mo) | Agentic Commerce — Business (contact us) | |---------|----------------------------|----------------------------------------|----------------------------------------| | Payments & Invoices | Yes | Yes | Yes | | Webhooks | 1 endpoint | 5 endpoints | 5 endpoints | | Multi-currency conversion | No | Yes | Yes | | L402 (server-side) | Capped | Yes | Yes | | Refunds | By request | By request | By request | **Refunds are not on any plan.** No tier turns them on — `refundsEnabled` is a per-account flag an operator sets on request. That is why the refunds `403` sends `required_plan: null` and `action_required: "contact_support"`: upgrading would not grant the feature. **Resolution:** Read `action_required`, not the plan name. - `contact_support` — email support@lightningenable.com. Upgrading will not help. - `upgrade_plan` — the plan named in `required_plan` grants the feature. If `required_plan` already matches your `current_plan`, the account flag was never applied to your row; contact support rather than buying the plan again. Key your handling on the `feature` field. `current_plan` is the normalized tier — one of `free`, `individual`, or `l402` — even when your account was created under an older tier id. There are two exceptions: an account carrying a tier value the API does not recognize is echoed back raw, and an account with a blank tier (no plan on file) is reported as `null`; handle an unexpected value rather than assuming the enum. ### L402 Token Expired ```json { "error": "Forbidden", "message": "L402 token has expired", "details": "Token expired at 2026-01-09T12:00:00Z" } ``` **Resolution:** Request the resource again without the `Authorization` header to receive a fresh 402 challenge, pay the new invoice, and use the new token. ### L402 Path Not Allowed ```json { "error": "Forbidden", "message": "Token not valid for this path", "allowed": "/l402/proxy/api-a/*", "requested": "/l402/proxy/api-b/data" } ``` **Resolution:** L402 tokens are scoped to specific proxy paths. You need a separate token for each proxy. **See also:** [Error Reference](/api-reference/errors#subscription-errors) --- ## 413 Payload Too Large The L402 proxy enforces size limits on both request and response bodies. **Error response:** ```json { "error": "Payload Too Large", "message": "Request body size (2,500,000 bytes) exceeds the maximum allowed size (1,048,576 bytes)", "proxy_id": "my-api-1234" } ``` **Default limits:** | Direction | Default Limit | |-----------|---------------| | Request body (client to proxy) | 1 MB (1,048,576 bytes) | | Response body (target API to proxy) | 10 MB (10,485,760 bytes) | **Troubleshooting steps:** 1. Check if your request payload exceeds 1 MB. Reduce the payload size or split it into smaller requests. 2. If the target API returns responses larger than 10 MB, the proxy will return a `502 Bad Gateway` instead (see [502 Bad Gateway](#502-bad-gateway) below). 3. These limits are server-wide defaults configured via `L402:MaxProxyRequestBodyBytes` and `L402:MaxProxyResponseBodyBytes`. Contact support if your use case requires higher limits. **Note:** The size check applies both when `Content-Length` is present and when it is absent (streaming). In the streaming case, the proxy reads the body incrementally and rejects it as soon as it exceeds the limit. --- ## 429 Too Many Requests There are **two distinct 429s**. Tell them apart by the `error` string in the body. ### Rate limit exceeded (too many requests) Rate limiting protects system stability. When you exceed your quota, you receive: ```json { "error": "Too many requests", "message": "Rate limit exceeded. Please try again later.", "retryAfter": 60 } ``` The wait time is the `retryAfter` field in the **JSON body** (seconds). The API does **not** emit `X-RateLimit-Limit`/`X-RateLimit-Remaining`/`X-RateLimit-Reset` or `Retry-After` headers for this limiter — don't write monitoring code that parses those headers. **Rate limits by policy:** | Policy | Limit | Window | Applied To | |--------|-------|--------|------------| | Global | 100 req | 1 min | All requests (per API key, or per IP when anonymous) | | Read | 200 req | 1 min | GET operations | | Payment Create | 10 req | 1 min | POST /api/payments, POST /api/refunds, /api/checkout/* | | Write | 20 req | 1 min | Merchant self-service writes (e.g., key regeneration) | | Checkout Create | 5 req | 1 min | Stripe checkout session creation | | Admin | 30 req | 1 min | Internal admin endpoints | | L402 Proxy | 100 req | 1 min | /l402/proxy/* | **Troubleshooting steps:** 1. **Read `retryAfter` from the JSON body** and wait that many seconds before retrying. 2. **Use webhooks instead of polling.** If you are polling payment status, switch to webhook notifications. Webhooks push events to your server in real time and consume zero API quota. 3. **Cache responses.** Exchange rates and payment status do not change every second. Cache GET responses for a reasonable TTL. 4. **Implement exponential backoff** with jitter for retries on 429 responses. ### Too many failed authentication attempts (auth-failure throttle) A different 429: more than 20 **failed** authentications (missing or invalid API key) from your IP within a 60-second window blocks further authenticated requests from that IP. This one **does** set a `Retry-After` header: ```http HTTP/1.1 429 Too Many Requests Retry-After: 42 { "error": "Too many failed authentication attempts", "message": "Slow down and try again in 42 seconds" } ``` If you see this, your integration is repeatedly sending a wrong or stale API key — **fix the key, don't retry**. Retrying with the same bad key records more failures and keeps the block engaged. Verify your key at **Dashboard → Settings**, update your configuration, then wait out the `Retry-After` seconds. **See also:** [Rate Limiting](/api-reference/rate-limiting) --- ## 502 Bad Gateway A `502` means the proxy or API could not get a valid response from an upstream service. ### L402 Proxy: Target API Unreachable ```json { "error": "Bad Gateway", "message": "Unable to connect to the target API", "proxy_id": "my-api-1234", "details": "Connection refused" } ``` **Troubleshooting steps:** 1. Verify the target API is online by making a direct request to the `targetBaseUrl`. 2. Check that the target URL is correct in your proxy configuration. 3. Test connectivity using the proxy test endpoint: ```bash curl -X POST https://api.lightningenable.com/api/proxy/{proxyId}/test \ -H "X-API-Key: your-api-key" ``` ### L402 Proxy: SSRF Protection Blocking Requests The L402 proxy includes SSRF (Server-Side Request Forgery) protection that blocks requests to internal network addresses. If your target URL resolves to a private IP, the proxy will reject it with a `502`. **Blocked addresses include:** - `localhost`, `127.0.0.1`, `::1` - Private IP ranges: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16` - Link-local addresses: `169.254.0.0/16` - Loopback hostname variants: `localhost.localdomain`, `ip6-localhost` - Hostnames ending in `.localhost` **Resolution:** The target API must be hosted on a publicly routable IP address. If your API runs on a private network, expose it through a reverse proxy or API gateway with a public hostname first. **Note:** SSRF validation occurs both when you create/update a proxy (at configuration time) and again at request time (to protect against DNS rebinding attacks where a domain initially resolves to a public IP but later changes to an internal IP). ### L402 Proxy: Response Too Large ```json { "error": "Bad Gateway", "message": "Response from target API (15,000,000 bytes) exceeds the maximum allowed size (10,485,760 bytes)", "proxy_id": "my-api-1234" } ``` **Resolution:** The target API returned a response larger than 10 MB. Either configure the target API to return smaller responses (pagination, filtering) or contact support about raising the limit. ### Strike API Errors Errors from Strike surface with the upstream Strike status code in the error details — a `401`/`403` from Strike means your Strike API key was rejected; a `5xx` means Strike itself is having trouble. **Troubleshooting steps:** 1. Validate your Strike API key against Strike's API: ```bash curl -X POST https://api.lightningenable.com/api/merchant/validate-strike \ -H "X-API-Key: your-merchant-api-key" ``` If validation fails, re-save the key (`PUT /api/merchant/strike-key` or the dashboard) and confirm it has the required scopes (`partner.receive-request.read`, `partner.receive-request.create`, `partner.webhooks.manage`). 2. **Refund failures:** Strike has no dedicated refund API — Lightning Enable processes Strike refunds as new **outgoing payments** to the customer's Lightning invoice. A refund can therefore fail if your Strike account balance is insufficient to send the payment. 3. **Delayed status updates:** Strike webhooks are "thin" (they carry only an entity ID), so Lightning Enable fetches the full payment details from Strike's API after each webhook. A transient Strike API error during that fetch can briefly delay a status update; use `POST /api/payments/{invoiceId}/sync` to force a re-check. 4. Check if Strike is experiencing an outage. **See also:** [Strike Setup](/strike-setup/account-setup) ### OpenNode API Errors ```json { "error": "OpenNode API error", "details": "Invalid API key" } ``` **Troubleshooting steps:** 1. Verify your OpenNode API key is correctly configured via the merchant settings endpoint, and validate it with `POST /api/merchant/validate-opennode`. It must be a **production** key — the hosted platform does not use OpenNode's dev environment. 2. Ensure your OpenNode account is active and verified. 3. Check if OpenNode is experiencing an outage. 4. If you see repeated 5xx errors from OpenNode, the circuit breaker may open (returning `503`). It auto-recovers after 30 seconds. **See also:** [OpenNode Setup](/opennode-setup/account-setup) --- ## Webhook Delivery Failures Webhooks are how Lightning Enable notifies your server about payment events. If they are not arriving, use this checklist to diagnose the problem. ### Webhooks Not Received **Step 1: Check your webhook URL configuration.** ```bash curl https://api.lightningenable.com/api/merchant/me \ -H "X-API-Key: your-api-key" ``` Verify the `webhookUrl` field in the response is correct and uses HTTPS (required in production). Common delivery failures, by the status code your endpoint returns: | Status Code | Meaning | Fix | |-------------|---------|-----| | 0 / timeout | Your server did not respond within 30 seconds | Return 200 immediately, process asynchronously | | 403 | Firewall blocking inbound requests | Allow inbound HTTPS from the internet to your webhook path and verify `X-LightningEnable-Signature` for authenticity (Lightning Enable's outbound IPs are not static, so don't rely on an IP allowlist) | | 404 | Wrong webhook path | Correct the URL in merchant settings | | 500 | Your handler threw an error | Fix the error in your webhook handler | **Step 2: Test your endpoint manually.** ```bash curl -X POST https://your-site.com/webhooks/lightning \ -H "Content-Type: application/json" \ -d '{"event": "payment.completed", "data": {"invoiceId": "test"}}' ``` If this returns a non-200 response, the problem is on your server. **Step 3: Recover the missed event.** Failed deliveries are retried with exponential backoff (30s/60s/120s/240s/480s, 5 retry attempts, ~16-minute window; each attempt has a 10-second timeout). If your endpoint was down longer than that, reconcile with the authoritative status: ```bash # Authoritative payment status curl https://api.lightningenable.com/api/payments/{invoiceId} \ -H "X-API-Key: YOUR_API_KEY" # Or force a re-check against your payment provider curl -X POST https://api.lightningenable.com/api/payments/{invoiceId}/sync \ -H "X-API-Key: YOUR_API_KEY" ``` ### Webhook Signature Verification Failing Lightning Enable signs outbound webhooks with the `X-LightningEnable-Signature` header using the format: ``` X-LightningEnable-Signature: t={timestamp},v1={hmac_sha256_signature} ``` **Common causes of signature verification failure:** 1. **Wrong secret.** Make sure you are using the correct webhook secret. For payment webhooks, this is the secret you configured for your merchant account. For Stripe subscription events forwarded by Lightning Enable, this is the `SubscriptionForwardSecret`. 2. **Parsed body vs. raw body.** The HMAC must be computed over the **raw request body bytes**, not over a re-serialized JSON object. Parsing and re-serializing JSON can change key ordering, whitespace, or unicode escaping, producing a different signature. 3. **Incorrect algorithm.** Use HMAC-SHA256. 4. **Timestamp drift.** The `t=` prefix contains the timestamp when the signature was generated. If you are validating timestamp freshness, allow a reasonable tolerance (e.g., 5 minutes). **Verification example (Node.js):** ```javascript const crypto = require('crypto'); function verifyWebhook(rawBody, signatureHeader, secret) { // Parse "t=123456,v1=abcdef..." const parts = Object.fromEntries( signatureHeader.split(',').map(p => p.split('=')) ); const timestamp = parts.t; const signature = parts.v1; const payload = `${timestamp}.${rawBody}`; const expected = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } ``` **See also:** [Webhooks](/api-reference/webhooks) --- ## Common L402 Errors These errors occur when using L402 tokens to access proxy-protected endpoints. ### Invalid Preimage Format **Error:** ```json { "error": "Invalid preimage format: must be exactly 64 hex characters" } ``` **Cause:** The preimage in your `Authorization: L402 :` header is not the correct format. **Requirements:** - Exactly 64 hexadecimal characters (representing 32 bytes) - Lowercase hex encoding (e.g., `a1b2c3d4...`) - No `0x` prefix **Troubleshooting steps:** 1. Check your wallet output. Some wallets return the preimage in base64 instead of hex. Convert it: ```bash echo -n "" | base64 -d | xxd -p -c 64 ``` 2. Verify the preimage length. If it is shorter or longer than 64 characters, it is the wrong value. Some wallets return the payment hash (which is the SHA-256 of the preimage), not the preimage itself. 3. Ensure you are not accidentally including whitespace or newlines. ### Preimage Does Not Match Payment Hash **Error (via `X-L402-Error` header):** ``` Preimage does not match payment hash ``` **Cause:** `SHA256(your_preimage) != payment_hash_in_macaroon`. This means either: - You are using the preimage from a **different** payment. - Your wallet returned the wrong value (e.g., payment hash instead of preimage). - The invoice expired and you paid a different invoice but are using the old macaroon. **Resolution:** Pay the specific invoice from the 402 response and use the macaroon and preimage from the same challenge. The macaroon and preimage are a matched pair -- you cannot mix them across different 402 challenges. ### Cross-Tenant Token Reuse **Error:** ```json { "error": "Token not valid for this merchant" } ``` **Cause:** Each L402 token is bound to a specific merchant via a `merchant_id` caveat. You cannot use a token obtained from one merchant's proxy to access a different merchant's proxy. **Related errors:** | Error Message | Meaning | |---------------|---------| | `Token requires merchant context but none was provided` | The token has a merchant_id caveat but the request context does not | | `Token missing required merchant_id caveat` | The request context has a merchant ID but the token does not contain a merchant_id caveat | | `Token not valid for this merchant` | The token's merchant_id does not match the proxy's merchant | **Resolution:** Obtain a new token by requesting the correct proxy endpoint without an `Authorization` header to receive a fresh 402 challenge. ### Token Not Valid for Path **Error:** ```json { "error": "Forbidden", "message": "Token not valid for path '/l402/proxy/api-b/data'. Token is bound to '/l402/proxy/api-a/*'" } ``` **Cause:** L402 tokens contain a path caveat restricting which endpoints they can access. A token issued for one proxy cannot be used on a different proxy. **Resolution:** Request a new token from the correct proxy endpoint. ### Amount Mismatch (Cross-Endpoint Replay) **Cause:** A token paid at one price tier (e.g., 10 sats for a demo endpoint) cannot be used on an endpoint with a different price (e.g., 50 sats for a premium endpoint), even if both are under the same proxy. **Resolution:** Request a new token for the specific endpoint you want to access. ### L402 Verification Failed **Error (via `X-L402-Error` header):** ``` L402 verification failed ``` **Cause:** The macaroon signature is invalid. This usually means: - The macaroon was tampered with or corrupted. - The server's `L402_ROOT_KEY` was rotated, invalidating all previously issued macaroons. **Resolution:** Request a fresh 402 challenge and pay the new invoice. --- ## Still Stuck? If this guide did not resolve your issue: 1. Check the [Error Reference](/api-reference/errors) for the exact error message you are seeing. 2. Review the [FAQ](/faq) for common questions. 3. Email **support@lightningenable.com** with: - The exact error response (HTTP status code + JSON body) - The request you are making (endpoint, headers, method) - Your merchant ID or API key prefix (first 10 characters only -- never share the full key) - Any relevant correlation IDs from 500 error responses