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:
{
"error": "API key required",
"message": "Please provide API key in X-API-Key header"
}
Troubleshooting steps:
- Verify you are sending the
X-API-Keyheader with every request:curl -H "X-API-Key: le_merchant_abc123..." https://api.lightningenable.com/api/payments - Confirm the key has no extra whitespace or line breaks. Copy it fresh from your subscription confirmation email or the admin dashboard.
- Check that you are using the correct key for the correct environment (development vs. production). Development keys work with
dev-api.opennode.com; production keys work withapi.opennode.com. - 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
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 below).
Troubleshooting steps:
- Log into your Stripe customer portal to check your subscription status.
- 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/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 <macaroon>:<preimage>"
}
}
What to do:
- Pay the Lightning invoice included in the
l402.invoicefield using any Lightning wallet. - Obtain the preimage (proof of payment) from the wallet after payment succeeds.
- Retry the original request with the
Authorizationheader:curl https://api.lightningenable.com/l402/proxy/{proxyId}/endpoint \
-H "Authorization: L402 <macaroon>:<preimage>" - 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, 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
{
"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
{
"error": "Subscription required",
"message": "Your plan tier requires an active subscription. Please subscribe to continue using the service.",
"current_plan": "standard",
"action_required": "subscribe"
}
Resolution: Subscribe at lightningenable.com. All paid plans require an active Stripe subscription.
Subscription Not Active (Past Due / Canceled)
{
"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 the /api/stripe/portal endpoint to update your payment method or renew.
Feature Not Available on Your Plan
{
"error": "Feature not available",
"message": "Refund processing is not available on your current plan.",
"feature": "refunds",
"current_plan": "pilot",
"required_plan": "starter",
"action_required": "upgrade_plan"
}
Plan feature matrix:
| Feature | Kentico Commerce ($249/mo) | Agentic Commerce ($299/mo) |
|---|---|---|
| Payments & Invoices | Yes | Yes |
| Webhooks | Yes | Yes |
| Refunds | Yes | Yes |
| L402 Proxy (server-side) | No | Yes |
Resolution: Upgrade to the plan that includes the feature you need. The error response includes the required_plan field.
L402 Token Expired
{
"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
{
"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 Code Reference
413 Payload Too Large
The L402 proxy enforces size limits on both request and response bodies.
Error response:
{
"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:
- Check if your request payload exceeds 1 MB. Reduce the payload size or split it into smaller requests.
- If the target API returns responses larger than 10 MB, the proxy will return a
502 Bad Gatewayinstead (see 502 Bad Gateway below). - These limits are server-wide defaults configured via
L402:MaxProxyRequestBodyBytesandL402: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
Rate limiting protects system stability. When you exceed your quota, you receive a 429 response.
Error response:
{
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"code": "RATE_LIMIT_EXCEEDED",
"details": {
"limit": 100,
"window": "1 minute",
"retryAfter": 45
}
}
Response headers:
Retry-After: 45
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704067200
Rate limits by policy:
| Policy | Limit | Window | Applied To |
|---|---|---|---|
| Global | 100 req | 1 min | All authenticated requests (per API key) |
| Read | 200 req | 1 min | GET operations |
| Payment Create | 10 req | 1 min | POST /api/payments, POST /api/refunds |
| Checkout Create | 5 req | 1 min | Stripe checkout session creation |
| Admin | 30 req | 1 min | /api/admin/* endpoints |
| L402 Proxy | 100 req | 1 min | /l402/proxy/* |
Troubleshooting steps:
- Read the
Retry-Afterheader and wait that many seconds before retrying. - 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.
- Cache responses. Exchange rates and payment status do not change every second. Cache GET responses for a reasonable TTL.
- Implement exponential backoff with jitter for retries on 429 responses.
- Monitor the
X-RateLimit-Remainingheader proactively in every response to detect approaching limits before you hit them.
See also: 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
{
"error": "Bad Gateway",
"message": "Unable to connect to the target API",
"proxy_id": "my-api-1234",
"details": "Connection refused"
}
Troubleshooting steps:
- Verify the target API is online by making a direct request to the
targetBaseUrl. - Check that the target URL is correct in your proxy configuration.
- Test connectivity using the proxy test endpoint:
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
{
"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.
OpenNode API Errors
{
"error": "OpenNode API error",
"details": "Invalid API key"
}
Troubleshooting steps:
- Verify your OpenNode API key is correctly configured via the merchant settings endpoint.
- Ensure your OpenNode account is active and verified.
- Check if OpenNode is experiencing an outage.
- 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
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.
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).
Step 2: Check webhook delivery logs.
curl https://api.lightningenable.com/api/webhooks/logs \
-H "X-API-Key: your-api-key"
Look for entries with "status": "failed" and check the statusCode and attempts fields. Common failures:
| Status Code | Meaning | Fix |
|---|---|---|
| 0 / timeout | Your server did not respond within 30 seconds | Return 200 immediately, process asynchronously |
| 403 | Firewall blocking inbound requests | Whitelist Lightning Enable IP addresses |
| 404 | Wrong webhook path | Correct the URL in merchant settings |
| 500 | Your handler threw an error | Fix the error in your webhook handler |
Step 3: Test your endpoint manually.
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 4: Check retry status. Lightning Enable retries failed webhooks up to 5 times:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
After 5 failed attempts, the webhook is marked as permanently failed.
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:
- 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. - 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.
- Incorrect algorithm. Use HMAC-SHA256.
- 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):
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
Common L402 Errors
These errors occur when using L402 tokens to access proxy-protected endpoints.
Invalid Preimage Format
Error:
{
"error": "Invalid preimage format: must be exactly 64 hex characters"
}
Cause: The preimage in your Authorization: L402 <macaroon>:<preimage> header is not the correct format.
Requirements:
- Exactly 64 hexadecimal characters (representing 32 bytes)
- Lowercase hex encoding (e.g.,
a1b2c3d4...) - No
0xprefix
Troubleshooting steps:
- Check your wallet output. Some wallets return the preimage in base64 instead of hex. Convert it:
echo -n "<base64-preimage>" | base64 -d | xxd -p -c 64 - 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.
- 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:
{
"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:
{
"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_KEYwas 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:
- Check the Error Code Reference for the exact error code and message you are seeing.
- Review the FAQ for common questions.
- 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