LNURL Integration Development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
LNURL Integration Development
Medium
~2-3 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1217
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1046
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

LNURL Protocol Integration

Lightning Network solved Bitcoin payment speed problem, but created new one: user needs copy invoice from seller's wallet into own wallet — this inconvenient. LNURL — set of protocols on top of Lightning allowing wallet to automatically request invoice via HTTP by scanning QR code once. From UX perspective approaches regular payment systems.

LNURL Protocol Family

LNURL — not single protocol, but several specs (LUD — Lightning URL Definitions). Each solves specific task:

LUD Protocol Purpose
LUD-01 LNURL-pay Payment: wallet requests invoice from seller's server
LUD-03 LNURL-withdraw Withdrawal: wallet receives funds by link
LUD-04 LNURL-auth Authentication via Lightning key (passwordless login)
LUD-06 LNURL-channel Channel opening
LUD-12 Lightning Address Format [email protected] for LNURL-pay

For accepting payments important LNURL-pay and Lightning Address.

How LNURL-pay Works

Entire process — two HTTP requests between wallet and server:

Step 1. User scans QR. Wallet sees lnurl1dp68gurn8ghj7um9wfmxjcm99e3k7mf0v9cxj0m385ekvcenxc6r2c35xvukxefcv5mkvv34x5ekzd3ev56nyd3hhgarjv4ehcmn9wsh8xmmrd9skcnjv... (bech32 encoded HTTPS URL). Wallet decodes and GET to this URL.

Step 2. Server returns metadata:

{
  "tag": "payRequest",
  "callback": "https://merchant.com/lnurl/pay/invoice",
  "minSendable": 1000,
  "maxSendable": 100000000,
  "metadata": "[[\"text/plain\",\"Payment to My Shop\"],[\"image/png;base64\",\"iVBORw0...\"]]"
}

Step 3. User enters amount. Wallet GET callback with amount parameter (in millisatoshi):

GET https://merchant.com/lnurl/pay/invoice?amount=10000

Step 4. Server generates Lightning invoice via own LN node and returns:

{
  "pr": "lnbc100n1pj...",
  "routes": [],
  "successAction": {
    "tag": "message",
    "message": "Payment confirmed! Order #12345"
  }
}

Step 5. Wallet pays invoice. After successful payment shows successAction.

Implementing LNURL-pay Server

Need Lightning node (LND or Core Lightning) for invoice generation. Example on Node.js with LND via gRPC:

import express from 'express';
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import { bech32 } from 'bech32';

const app = express();

// LNURL-pay step 1: metadata
app.get('/lnurl/pay/:paymentId', async (req, res) => {
  const { paymentId } = req.params;
  const callbackUrl = `https://${req.hostname}/lnurl/pay/${paymentId}/invoice`;
  
  // Encode URL in lnurl bech32 (for QR code)
  const lnurlEncoded = encodeLnurl(callbackUrl);
  
  res.json({
    tag: 'payRequest',
    callback: callbackUrl,
    minSendable: 1000,          // 1 sat in millisatoshi
    maxSendable: 100_000_000,   // 0.001 BTC
    metadata: JSON.stringify([
      ['text/plain', `Payment for order ${paymentId}`],
    ]),
  });
});

// LNURL-pay step 2: invoice generation
app.get('/lnurl/pay/:paymentId/invoice', async (req, res) => {
  const { paymentId } = req.params;
  const amountMsat = parseInt(req.query.amount as string);
  
  if (!amountMsat || amountMsat < 1000) {
    return res.status(400).json({ status: 'ERROR', reason: 'Invalid amount' });
  }
  
  try {
    const invoice = await lndClient.addInvoice({
      value_msat: amountMsat,
      memo: `Order ${paymentId}`,
      expiry: 3600, // 1 hour
    });
    
    // Save in DB: link invoice to paymentId
    await db.saveInvoice({
      paymentHash: invoice.r_hash,
      paymentId,
      amountMsat,
    });
    
    res.json({
      pr: invoice.payment_request,
      routes: [],
      successAction: {
        tag: 'message',
        message: `Order ${paymentId} confirmed!`,
      },
    });
  } catch (err) {
    res.status(500).json({ status: 'ERROR', reason: 'Failed to generate invoice' });
  }
});

function encodeLnurl(url: string): string {
  const words = bech32.toWords(Buffer.from(url, 'utf8'));
  return bech32.encode('lnurl', words, 1023).toUpperCase();
}

Lightning Address: [email protected]

Lightning Address (LUD-12) — most convenient UX. Instead of QR code user enters address like email. Wallet automatically requests https://domain.com/.well-known/lnurlp/username.

// Route for Lightning Address
app.get('/.well-known/lnurlp/:username', async (req, res) => {
  const { username } = req.params;
  
  const user = await db.getUserByLnAddress(username);
  if (!user) {
    return res.status(404).json({ status: 'ERROR', reason: 'User not found' });
  }
  
  res.json({
    tag: 'payRequest',
    callback: `https://${req.hostname}/lnurl/lightning-address/${username}`,
    minSendable: 1000,
    maxSendable: 10_000_000_000,
    metadata: JSON.stringify([
      ['text/identifier', `${username}@${req.hostname}`],
      ['text/plain', `Payment to ${username}`],
    ]),
    commentAllowed: 144, // support comments up to 144 characters
  });
});

After this [email protected] works as Lightning Address in any compatible wallet (Phoenix, Wallet of Satoshi, Zeus, Breez).

LNURL-auth: Passwordless Login

LNURL-auth allows users to login via Lightning wallet without password. Wallet signs challenge with private key derived from Lightning seed.

import crypto from 'crypto';

// Generate challenge for login
app.get('/auth/lnurl', (req, res) => {
  const k1 = crypto.randomBytes(32).toString('hex');
  
  // Save k1 in Redis with TTL 5 minutes
  redis.setex(`lnurl_auth:${k1}`, 300, 'pending');
  
  const lnurlAuthUrl = `https://${req.hostname}/auth/callback?tag=login&k1=${k1}`;
  const encoded = encodeLnurl(lnurlAuthUrl);
  
  res.json({ lnurl: encoded, k1 });
});

// Callback from wallet (signature of k1)
app.get('/auth/callback', async (req, res) => {
  const { k1, sig, key } = req.query as Record<string, string>;
  
  const status = await redis.get(`lnurl_auth:${k1}`);
  if (!status) {
    return res.json({ status: 'ERROR', reason: 'Unknown k1' });
  }
  
  // Verify ECDSA signature (secp256k1)
  const isValid = verifyLnurlAuthSignature(k1, sig, key);
  if (!isValid) {
    return res.json({ status: 'ERROR', reason: 'Invalid signature' });
  }
  
  // Mark k1 as authenticated with public key
  await redis.setex(`lnurl_auth:${k1}`, 300, `authenticated:${key}`);
  
  res.json({ status: 'OK' });
});

Frontend polls k1 status — once wallet signs, user logged in.

Infrastructure Requirements

Lightning node — mandatory. Options: LND (Go, most widespread, gRPC API), Core Lightning / CLN (C, UNIX socket + REST), Eclair (Scala, used by Acinq/Phoenix). For production: dedicated VPS with 4GB+ RAM, SSD, stable internet. Node must have inbound liquidity for accepting payments.

Hosted solutions for quick start: Voltage.cloud (managed LND), Alby Hub (self-custody but simplified), Strike API (custodial, but no own node needed). For serious use with high volumes — only own node.

TLS and domain mandatory: LNURL requires HTTPS. Self-signed certificate won't pass — need Let's Encrypt or similar.

Monitoring: channel balance (alert when < 10% inbound liquidity), invoice expiry, failed payment attempts. LND Metrics exports Prometheus-compatible metrics out of box.