Development and integration of the LNURL protocol for Lightning payments

Manual invoice issuance slows payment acceptance and leads to checkout errors. We develop and implement turnkey LNURL integration: we set up automatic invoice issuance, incoming payment tracking, and reporting. This reduces the time to process a single payment several times over and removes manual operations from employees.

Blockchain Development Services

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1376
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1350
  • B2B Advance company logo design
    B2B Advance company logo design
    798
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1088
  • AIDER company logo development
    AIDER company logo development
    1034
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1130

Why LNURL integration breaks down during invoice issuance

LNURL integration most often breaks down not at payment acceptance but at invoice issuance: the request went to the node, a response came back, but the invoice itself never reached the user or the billing system.

Externally it looks like “payment isn't going through,” although the network has nothing to do with it — the path from request to entry in the accounting system is broken. The user sees an error and leaves, while an expired invoice remains hanging in the database.

The first source of failure is state desynchronization. The invoice has been issued and paid, but billing doesn't know about it because the status notification was lost during a connection drop or failed signature verification.

As a result, a closed invoice remains in “awaiting payment” status, the client pays a second time, and support has to deal with the refund.

Second — repeated requests with the same order identifier. The wallet repeats the request, the intermediate layer re-queries, and instead of one invoice, three or four settle in the system, some of them already paid.

Without idempotency by order key, such duplicates cannot be distinguished from genuinely new invoices.

Third — manual operations in billing. When automation doesn't reconcile, support reconciles manually: closes invoices, allocates incoming payments, searches for lost payments. This is not only hours of work but also a class of failures that repeats more often the higher the load.

We'll break down how this is closed: where idempotency keys are placed, how statuses are synchronized, and what eliminates manual operations.

What changes after implementation: measurable effects

After switching to LNURL integration, an invoice stops being a “request” and becomes a transaction. The user confirms the payment in the wallet, funds go via Lightning Network, and confirmation arrives upon settlement, not after accumulating confirmations in the blockchain.

Credit speed is measured in seconds, and this changes not only the customer experience but also the settlement logic on the service side.

Second effect — payment automation. The incoming payment event reaches the application backend and is matched to the order by invoice identifier, so the share of automatically closed invoices reaches 90–97%.

The remainder is not integration failures but business-logic exceptions: partial payment, refund, manual balance adjustment.

Third effect — an eliminated class of failures. Previously the most expensive scenario was not payment failure but “the payment went through but the system didn't see it”: the order remains unpaid, the client writes to support, the operator searches for the transaction manually.

Idempotent processing by identifier and explicit invoice cancellation by expiry close this scenario entirely — the share of manual operations drops to single-digit percentages.

In operations this is visible in support load: “where is my payment” tickets become fewer, refunds speed up, and invoice closure time stops depending on operator workload.

We capture these metrics before implementation and again afterward so the effect is measurable rather than declarative.

Payment acceptance modes and when each is appropriate

The acceptance mode determines not the interface but who issues the invoice and how long it remains valid. A static invoice lives until the key is revoked, a one-time invoice until the timeout expires, and automatic payouts do not create an incoming invoice at all. Hence the first choice during integration: deterministic invoice versus dynamic.

Second layer — flow direction. One part of LNURL addresses a payment to us, the other gives the recipient the right to withdraw funds; the mode's constraints are set precisely by this side. The two cannot be mixed in one codebase — the same QR will either receive or send.

Mode When appropriate Mode constraints
Static invoice Storefront, donations, one QR for an entire campaign The amount is set by the payer; repeat payments differ only by metadata
One-time invoice per order Cart payment with an exact amount and expiry Expires by timeout; each order is a new code
Automatic payouts Refunds, cashback, payouts to partners The link is one-time and secret: whoever intercepts it takes the funds
Payout without invoice Mass transfers to a known node No receipt from the recipient — you need your own registry
Held invoice A transaction with service confirmation before settlement Requires node support and a reserve timeout, otherwise the payment hangs

In practice, the setup is assembled from two or three modes: static invoices for the incoming flow, one-time invoices for fixed-amount settlements, and automatic payouts for mass payouts.

The link between them is the order identifier in the invoice metadata: without it, a payment via a static code cannot be matched to a specific operation, and reconciliation collapses on the very first refund.

How implementation proceeds: stages of engineering work

LNURL implementation is not adding a “pay” button but rebuilding the payment path: from invoice generation to confirmation and refund. Therefore each stage ends with an artifact that is checked before moving to the next.

  1. Payment scheme audit. We examine current scenarios: who initiates the payment, where the operation state is stored, what limits exist, and how connection drops are handled. The output is a list of places for embedding LNURL and a list of risks.
  2. Interaction scheme. We fix the flows between services: request generation, confirmation, callback, status changes. We mark the points where idempotency and timeouts are needed so that a retry does not turn into a double charge.
  3. Prototype on the test network. We assemble a minimal setup and run basic operations: payment, withdrawal, cancellation. This way, discrepancies in protocol interpretation surface before changes to the main product.
  4. Product integration. We wire up the services: queues, retries on failures, operation logging, and status display to the user. Here we also configure observability — the share of successful routes and confirmation time.
  5. Test scenarios. We test not only the happy path: connection drop, repeated request, expired invoice, node restart, failure recovery. Each scenario has expected behavior and a record in the report.
  6. Handover to maintenance. We provide documentation, configurations, an on-call procedure, and alerts. The customer's team receives training on incident analysis, and thereafter support follows the procedure.

What you keep: configurations, schemes, procedures

The documentation set is part of the result, not an appendix to it. We hand over artifacts to your repository and your infrastructure so that maintenance can be run by your own team without coming back to us for every configuration change.

  • Service and node configurations: connection parameters, payment amount limits, settlement wait timeouts, invoice templates — with notes on which values are calculated for the current load profile and which should be changed as it grows.
  • Interaction schemes: the call sequence wallet → LNURL endpoint → node, failure points, and where each operation is recorded. With it, incident analysis takes minutes rather than hours of searching through logs.
  • Access policies: roles, key issuance and rotation, request rate and volume limits, environment isolation rules — the test environment must not see production channels.
  • Operations procedure: what to monitor, alert thresholds, who acts and in what order when channel state is desynchronized or a node goes offline.
  • Rollback and migration procedures: switching to a backup node, reverting to the previous configuration, migrating payment history without losing reconciliation.
  • Integration documentation: endpoint descriptions, request and response formats, ready examples for payment, refund, and retry.
  • Acceptance checklist: a list of scenarios covered by checks, including connection drop and repeated request submission.

We hand over in a format that can be read without us: schemes in source files, configurations under version control, procedures as working instructions for the on-call engineer. Describe your task — we will come back with an estimate and work plan.

Case study: payment acceptance without manual invoice issuance

The customer is a digital goods service with a stream of small payments. Invoices were issued manually: an operator created an invoice in the wallet, sent the link to the buyer, and reconciled incoming payments by eye against the statement.

At peak, this was 100% manual operations, up to four minutes per invoice, and 6–8% of orders where the payment arrived but was not matched to the buyer.

Payment acceptance was moved to LNURL-pay on top of Lightning Network: the client address returns a one-time invoice on request, and payment status arrives as a notification immediately after settlement.

The key element is a retry-resistant handler: the payment is linked to the order by invoice identifier, duplicates are not created, and underpayment and overdue receive separate statuses.

What was changed in the process

Invoice issuance and reconciliation were separated into two layers. The first is a public address that generates an invoice for a specific order with a lifetime limit.

The second is a reconciliation service: it matches the payment to the order, writes an event to the journal, and only then authorizes goods delivery.

Separately, we added recovery of missed events: once a minute a job rereads the statuses of open invoices, so a notification failure does not turn into “paid but not delivered.”

Metrics before and after

The share of manually issued invoices fell from 100% to zero — an invoice is generated in 1–2 seconds.

Reconciliation discrepancies decreased from 6–8% to zero, shift closing went from 2–3 hours to 10 minutes, and support requests on the topic “I paid but have no access” dropped by roughly 5 times.

How does LNURL integration differ from a permanent address?

The difference is in the nature of the identifier. A permanent address is the same payment detail that is published and used many times: a QR code on a page, a line in a signature, a link in a profile. It does not change, and every payment arrives at it as at the same address.

LNURL, whose specification is described by LNURL, is not an address but a handler. The wallet contacts it at the moment of payment, receives a one-time invoice with an amount, expiry, and parameters, while the published address remains only an entry point. After successful payment, the invoice is settled — it cannot be presented again.

Hence the main consequence — payment privacy. With a permanent address, an external observer sees all incoming transfers and links them to one owner: from the number and rhythm of incoming payments, the flow of funds and the recipient's activity can be reconstructed.

Automatic invoice issuance breaks this link — each invoice lives separately and does not point to the common address.

Reusing an address also gets in the way of settlements. A dynamic amount, comment, and expiry cannot be embedded in a static address, so a server-side layer still appears alongside it to calculate the price and verify payment. LNURL moves this logic to the moment the invoice is issued.

There is also a limitation: the handler must be available at the moment of payment — if the server does not respond, the wallet will not receive an invoice.

Therefore, for fixed scenarios a permanent address is appropriate, while LNURL is used where a variable amount, short invoice lifetime, and separation of payments from each other are needed.

Concerns and technical limitations

Connection loss on the client side is the most common source of doubt: the invoice has already been issued, but confirmation did not reach the client.

We do not rely on the response at the moment of request — the payment is linked to the order identifier, and the state is queried separately by a stable key.

After network recovery, the client sees the current status rather than creating a second invoice: a repeated request returns the same result instead of a new request.

We close repeated requests at the level of the operation identifier: the key is composed of the order number and amount, the server stores the result for this key for a limited time and returns it again without going back to the node.

Load is held by status caching and polling rate limiting — polling once every few seconds instead of a stream of identical requests, which are usually what brings the service down.

What happens if the connection drops at the moment of payment?

The payment remains pending — the invoice is not closed by the fact of connection loss, only by its actual issuance expiry. The client returns, repeats the request with the same key, and receives the current state: paid, expired, or pending. No duplicate request appears in accounting.

How does this reconcile with current accounting?

Each event is recorded in the journal with a key, status, and timestamp, and reconciliation is exported on a schedule and highlights discrepancies automatically.

Accounting entries are created once per operation, so manual corrections after a series of repeated requests are not required. If accounting already keeps order history, the integration connects to it without restructuring the data schema.

We agree on the response format and failure behavior at the design stage — for the specific accounting and load scenarios. Describe your task, and we will come back with an estimate and plan.

Let's discuss the task and come back with an estimate and plan

For the analysis to be substantive rather than a retelling of documentation, a few inputs are enough. We look at the current payment acceptance scheme, bottlenecks, and what already works — this is the basis for the implementation plan and scope of work.

  • Payment acceptance scheme: where the Lightning node is located, who holds the channels, how invoices are generated and closed.
  • What already exists for LNURL: Lightning Address, withdrawals via lnurl-withdraw, login via lnurl-auth.
  • Requirements for limits, invoice lifetime, routing fees, and behavior on failed payment.
  • Failure scenarios: repeat payment, cancellation, refund, repeated request from the wallet to the same address.
  • Access and environments: who owns the node keys, how the test setup is organized, and whether it is isolated from production.
  • Logs and metrics: what is written on failure and how you currently find out that a payment did not arrive.
  • Expected load: how many payments at peak and how many requests arrive at one address.

In response we provide a solution scheme with integration boundaries, a stage-by-stage scope of work, and a verification procedure — a set of test scenarios, acceptance criteria, and a rollback plan to the previous configuration. Describe your task — we will come back with an estimate and plan.