Skip to main content

Integration Guide

This guide walks you through integrating the Lightning Enable Standalone 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:

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:

export LIGHTNING_API_KEY="le_merchant_abc123..."

Creating Payments

Basic Payment

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

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

FieldTypeRequiredDescription
orderIdstringYesYour unique order identifier
amountdecimalYesPayment amount
currencystringYesCurrency code (USD, EUR, GBP, BTC)
descriptionstringNoPayment description
customerEmailstringNoCustomer email
customerNamestringNoCustomer name
successUrlstringNoRedirect URL on success
cancelUrlstringNoRedirect URL on cancel
metadataobjectNoCustom key-value data

Payment Response Fields

FieldTypeDescription
invoiceIdstringLightning Enable invoice ID
openNodeChargeIdstringOpenNode charge ID
statusstringPayment status (unpaid, paid, expired)
amountdecimalPayment amount
currencystringCurrency code
amountSatsintegerAmount in satoshis
lightningInvoicestringBOLT11 Lightning invoice
onchainAddressstringBitcoin address
hostedCheckoutUrlstringOpenNode checkout URL
createdAtdatetimeCreation timestamp
expiresAtdatetimeExpiration timestamp

Displaying Payment Options

Option 1: Hosted Checkout (Easiest)

Redirect customers to OpenNode's hosted checkout:

// 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:

<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js"></script>

<canvas id="qr-code"></canvas>

<script>
QRCode.toCanvas(
document.getElementById('qr-code'),
payment.lightningInvoice,
{ width: 256 }
);
</script>

Option 3: Copy Invoice

Let users copy the invoice to their wallet:

<input
type="text"
value="${payment.lightningInvoice}"
id="invoice-input"
readonly
/>
<button onclick="copyInvoice()">Copy Invoice</button>

<script>
function copyInvoice() {
document.getElementById('invoice-input').select();
navigator.clipboard.writeText(document.getElementById('invoice-input').value);
alert('Copied!');
}
</script>

Checking Payment Status

Polling

Query the status endpoint periodically:

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);

Receive instant notifications when payment status changes:

// Express.js webhook handler
app.post('/webhooks/lightning', express.json(), (req, res) => {
const { invoiceId, orderId, status } = req.body;

// Verify signature (important!)
const signature = req.headers['x-gateway-signature'];
if (!verifySignature(req.body, signature)) {
return res.status(401).send('Invalid signature');
}

if (status === 'paid') {
fulfillOrder(orderId);
}

res.status(200).send('OK');
});

See Webhooks Documentation for complete implementation.

Error Handling

Handle common error scenarios:

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

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<PaymentResponse> 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<PaymentResponse>();
}
}

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

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