Error Handling
Learn how to handle errors from the Lightning Enable API.
Error Response Format
All errors follow a consistent format:
{
"error": "Error Type",
"message": "Human-readable description",
"code": "MACHINE_READABLE_CODE",
"details": { }
}
| Field | Type | Description |
|---|---|---|
error | string | HTTP status text |
message | string | Human-readable error message |
code | string | Machine-readable error code |
details | object | Additional error context (optional) |
HTTP Status Codes
Success Codes
| Code | Meaning |
|---|---|
200 OK | Request succeeded |
201 Created | Resource created |
204 No Content | Request succeeded, no response body |
Client Error Codes
| Code | Meaning |
|---|---|
400 Bad Request | Invalid request parameters |
401 Unauthorized | Missing or invalid API key |
402 Payment Required | L402 payment needed |
403 Forbidden | Access denied (inactive, wrong plan, etc.) |
404 Not Found | Resource doesn't exist |
409 Conflict | Resource conflict (duplicate, etc.) |
422 Unprocessable Entity | Valid syntax but semantic error |
429 Too Many Requests | Rate limit exceeded |
Server Error Codes
| Code | Meaning |
|---|---|
500 Internal Server Error | Server error |
502 Bad Gateway | Upstream service error |
503 Service Unavailable | Temporary maintenance |
504 Gateway Timeout | Upstream timeout |
Error Codes Reference
Authentication Errors
MISSING_API_KEY
{
"error": "Unauthorized",
"message": "API key is required. Include X-API-Key header.",
"code": "MISSING_API_KEY"
}
Solution: Add X-API-Key header to your request.
INVALID_API_KEY
{
"error": "Unauthorized",
"message": "Invalid API key",
"code": "INVALID_API_KEY"
}
Solution: Verify your API key is correct and active.
MERCHANT_INACTIVE
{
"error": "Forbidden",
"message": "Merchant account is inactive",
"code": "MERCHANT_INACTIVE"
}
Solution: Check your subscription status or contact support.
Subscription Errors
SUBSCRIPTION_REQUIRED
{
"error": "Forbidden",
"message": "Active subscription required",
"code": "SUBSCRIPTION_REQUIRED"
}
Solution: Subscribe to a plan at lightningenable.com.
SUBSCRIPTION_EXPIRED
{
"error": "Forbidden",
"message": "Subscription has expired",
"code": "SUBSCRIPTION_EXPIRED"
}
Solution: Renew your subscription.
FEATURE_NOT_AVAILABLE
{
"error": "Forbidden",
"message": "Refunds not available on your plan",
"code": "FEATURE_NOT_AVAILABLE",
"details": {
"feature": "refunds",
"currentPlan": "pilot",
"requiredPlan": "production"
}
}
Solution: Upgrade to a plan with this feature.
Payment Errors
INVALID_AMOUNT
{
"error": "Bad Request",
"message": "Amount must be greater than 0",
"code": "INVALID_AMOUNT"
}
Solution: Provide a positive payment amount.
INVALID_CURRENCY
{
"error": "Bad Request",
"message": "Unsupported currency: XYZ",
"code": "INVALID_CURRENCY",
"details": {
"supported": ["USD", "EUR", "GBP", "BTC", "sats"]
}
}
Solution: Use a supported currency code.
MISSING_ORDER_ID
{
"error": "Bad Request",
"message": "Order ID is required",
"code": "MISSING_ORDER_ID"
}
Solution: Include orderId in your request.
DUPLICATE_ORDER_ID
{
"error": "Conflict",
"message": "Order ID already exists",
"code": "DUPLICATE_ORDER_ID",
"details": {
"orderId": "ORDER-123",
"existingInvoiceId": "inv_abc123"
}
}
Solution: Use unique order IDs for each payment.
PAYMENT_NOT_FOUND
{
"error": "Not Found",
"message": "Payment not found",
"code": "PAYMENT_NOT_FOUND"
}
Solution: Verify the invoice ID is correct.
Refund Errors
INVOICE_NOT_PAID
{
"error": "Bad Request",
"message": "Cannot refund unpaid invoice",
"code": "INVOICE_NOT_PAID"
}
Solution: Only refund invoices with paid status.
REFUND_EXCEEDS_PAYMENT
{
"error": "Bad Request",
"message": "Refund amount exceeds original payment",
"code": "REFUND_EXCEEDS_PAYMENT",
"details": {
"originalAmount": 49.99,
"requestedRefund": 75.00,
"previousRefunds": 0
}
}
Solution: Refund amount must not exceed payment amount.
INVALID_LIGHTNING_INVOICE
{
"error": "Bad Request",
"message": "Invalid Lightning invoice",
"code": "INVALID_LIGHTNING_INVOICE"
}
Solution: Provide a valid BOLT11 Lightning invoice.
INSUFFICIENT_BALANCE
{
"error": "Payment Failed",
"message": "Insufficient balance in OpenNode account",
"code": "INSUFFICIENT_BALANCE"
}
Solution: Add funds to your OpenNode account.
L402 Errors
L402_PAYMENT_REQUIRED
{
"error": "Payment Required",
"message": "Pay the Lightning invoice to access this resource",
"code": "L402_PAYMENT_REQUIRED",
"l402": {
"macaroon": "...",
"invoice": "...",
"amount_sats": 10
}
}
Solution: Pay the provided Lightning invoice.
INVALID_L402_CREDENTIAL
{
"error": "Unauthorized",
"message": "Invalid L402 credential",
"code": "INVALID_L402_CREDENTIAL",
"details": "Preimage does not match payment hash"
}
Solution: Verify macaroon and preimage are correct.
L402_TOKEN_EXPIRED
{
"error": "Forbidden",
"message": "L402 token has expired",
"code": "L402_TOKEN_EXPIRED"
}
Solution: Get a new token by paying again.
Rate Limiting Errors
RATE_LIMIT_EXCEEDED
{
"error": "Too Many Requests",
"message": "Rate limit exceeded",
"code": "RATE_LIMIT_EXCEEDED",
"details": {
"limit": 100,
"window": "1 minute",
"retryAfter": 45
}
}
Solution: Wait before retrying. See Retry-After header.
OpenNode Errors
OPENNODE_ERROR
{
"error": "Bad Gateway",
"message": "OpenNode API error",
"code": "OPENNODE_ERROR",
"details": {
"openNodeError": "Invalid API key"
}
}
Solution: Check your OpenNode API key configuration.
OPENNODE_TIMEOUT
{
"error": "Gateway Timeout",
"message": "OpenNode request timed out",
"code": "OPENNODE_TIMEOUT"
}
Solution: Retry the request.
Handling Errors
JavaScript
async function createPayment(orderId, amount, currency) {
try {
const response = await fetch('https://api.lightningenable.com/api/payments', {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({ orderId, amount, currency })
});
if (!response.ok) {
const error = await response.json();
throw new LightningError(error);
}
return response.json();
} catch (error) {
handleError(error);
}
}
class LightningError extends Error {
constructor(data) {
super(data.message);
this.code = data.code;
this.details = data.details;
}
}
function handleError(error) {
switch (error.code) {
case 'INVALID_API_KEY':
console.error('Check your API key configuration');
break;
case 'RATE_LIMIT_EXCEEDED':
const retryAfter = error.details?.retryAfter || 60;
console.log(`Rate limited. Retry after ${retryAfter}s`);
break;
case 'OPENNODE_ERROR':
console.error('OpenNode issue:', error.details?.openNodeError);
break;
default:
console.error('Error:', error.message);
}
}
C#
public class LightningEnableClient
{
public async Task<PaymentResponse> CreatePaymentAsync(PaymentRequest request)
{
var response = await _httpClient.PostAsJsonAsync("/api/payments", request);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadFromJsonAsync<ErrorResponse>();
throw new LightningException(error);
}
return await response.Content.ReadFromJsonAsync<PaymentResponse>();
}
}
public class LightningException : Exception
{
public string Code { get; }
public Dictionary<string, object>? Details { get; }
public LightningException(ErrorResponse error)
: base(error.Message)
{
Code = error.Code;
Details = error.Details;
}
}
// Usage
try
{
var payment = await client.CreatePaymentAsync(request);
}
catch (LightningException ex) when (ex.Code == "RATE_LIMIT_EXCEEDED")
{
var retryAfter = ex.Details?["retryAfter"] as int? ?? 60;
await Task.Delay(TimeSpan.FromSeconds(retryAfter));
// Retry
}
catch (LightningException ex)
{
_logger.LogError("Lightning API error: {Code} - {Message}", ex.Code, ex.Message);
}
Python
class LightningError(Exception):
def __init__(self, response):
self.code = response.get('code')
self.message = response.get('message')
self.details = response.get('details', {})
super().__init__(self.message)
def create_payment(order_id, amount, currency):
response = requests.post(
'https://api.lightningenable.com/api/payments',
headers={'X-API-Key': API_KEY, 'Content-Type': 'application/json'},
json={'orderId': order_id, 'amount': amount, 'currency': currency}
)
if not response.ok:
raise LightningError(response.json())
return response.json()
# Usage
try:
payment = create_payment('ORDER-123', 49.99, 'USD')
except LightningError as e:
if e.code == 'RATE_LIMIT_EXCEEDED':
retry_after = e.details.get('retryAfter', 60)
time.sleep(retry_after)
# Retry
elif e.code == 'INVALID_API_KEY':
print('Check your API key')
else:
print(f'Error: {e.message}')
Retry Strategy
Retryable Errors
| Error | Retry? | Strategy |
|---|---|---|
| 429 Rate Limited | Yes | Wait for Retry-After |
| 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 |
| 400 Bad Request | No | Fix request |
| 401 Unauthorized | No | Fix credentials |
| 404 Not Found | No | Check resource ID |
Exponential Backoff
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(resolve => setTimeout(resolve, delay));
}
}
}
function isRetryable(error) {
const retryableCodes = [
'RATE_LIMIT_EXCEEDED',
'OPENNODE_TIMEOUT',
'OPENNODE_ERROR'
];
return retryableCodes.includes(error.code) ||
error.status >= 500;
}
Best Practices
Always Check Status Codes
// Bad - assumes success
const data = await response.json();
// Good - check status first
if (!response.ok) {
const error = await response.json();
throw new Error(error.message);
}
const data = await response.json();
Log Errors with Context
function handleError(error, context) {
console.error({
code: error.code,
message: error.message,
details: error.details,
context: {
orderId: context.orderId,
timestamp: new Date().toISOString()
}
});
}
User-Friendly Messages
function getUserMessage(error) {
const messages = {
'INVALID_API_KEY': 'Authentication failed. Please try again.',
'PAYMENT_NOT_FOUND': 'Payment not found. It may have expired.',
'RATE_LIMIT_EXCEEDED': 'Too many requests. Please wait a moment.',
'OPENNODE_ERROR': 'Payment service temporarily unavailable.'
};
return messages[error.code] || 'An error occurred. Please try again.';
}
Next Steps
- Rate Limiting - API rate limits
- Authentication - API key setup
- Webhooks - Event notifications