---
title: Migration Guide
slug: migration-guide
docTags: 
createdAt: 2026-03-06T17:33:32.699Z
---

# Migrating from Direct LND

## Who This Guide Is For

You're currently integrating directly with LND — via gRPC, REST, or lncli — using TLS certificates and macaroons for authentication. You want to move to the Voltage Payments API, which gives you a single REST API for sending and receiving payments without managing gRPC stubs, protobuf definitions, macaroon files, or route-finding logic. This guide covers what changes regardless of your target setup, then links you to the step-by-step path that fits your needs.

## Why Migrate: Payments API vs. Direct LND

Before diving into the how, here's what you gain by moving from direct LND to the Payments API.

### Built-in Accounts and Fund Segregation

With direct LND, your node has a single on-chain wallet and a single pool of channel liquidity. If you serve multiple customers, products, or use cases, you need to build a double-entry ledger system on top of LND to track who owns what. This is complex, error-prone, and a significant engineering investment.

The Payments API gives you **wallets** — each with its own tracked balance — all backed by a single node. Fund segregation is handled for you at the API layer. You can create as many wallets as you need per environment without building or maintaining your own accounting system.

### Webhooks for Payment Notifications

LND doesn't have native webhooks. To know when a payment arrives or completes, you either poll or hold open a gRPC stream (`SubscribeInvoices`, `SubscribeChannelEvents`) and manage reconnection logic yourself.

The Payments API delivers **real-time webhook notifications** — HTTP POST requests to your endpoint for every payment event. Paired with a lightweight reconciliation loop as a safety net, this gives you reliable, event-driven payment monitoring without managing persistent connections.

### Payments Abstracted from Node Liquidity

When you use LND directly, every payment forces you to think about the underlying plumbing: channel balances, inbound vs. outbound liquidity, UTXO management, fee estimation, route finding. Your application code is tightly coupled to node operations.

The Payments API **separates payment logic from liquidity management**. Your application talks to wallets and payments — clean balances, simple send/receive calls, payments that just work. The LND node operates underneath as a liquidity reserve, but those concerns are isolated. You (and Voltage, via the Support Macaroon) manage channels and capacity separately from your payment integration.

With a **credit-backed** setup, this separation goes even further — there's no node to manage at all. With a **node-backed** setup, you still handle liquidity, but your payment code never has to know about it.

## Choose Your Migration Path

There are two ways to use the Voltage Payments API. Choose based on whether you want to keep running your own node.

|                        | **Node-backed**                                                                      | **Credit-backed**                                                            |
| ---------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| **Plan required**      | Enterprise                                                                           | Enterprise + Line of Credit approval                                         |
| **Infrastructure**     | You keep your Voltage LND node                                                       | No node — Voltage handles it                                                 |
| **Funding**            | Your node's on-chain + channel balance                                               | Line of Credit (collateral-backed)                                           |
| **Channel management** | You + Voltage (via Support Macaroon)                                                 | Not needed                                                                   |
| **Liquidity**          | You manage treasury; Voltage assists                                                 | Not needed                                                                   |
| **USD support**        | BTC only                                                                             | BTC wallets or USD reconciliation                                            |
| **Best for**           | Teams that need full node control, existing channel relationships, or custom routing | Teams that want zero infrastructure overhead and/or USD-denominated payments |

**Ready to start?**

- [Migrating to Node Backed Payments](docId\:BrkO-WDXk1DiOPuqWmFFs) — keep your node, use the API
- [Migrating to Credit Backed Payments](docId\:PpMDfT3_OtGSnxZ7ai6Ry) — drop your node, use a Line of Credit

***

## What Changes (Both Paths)

The sections below apply to both migration paths. Read through them before starting your step-by-step guide.

### Authentication

|                       | **Before (Direct LND)**                              | **After (Payments API)**                  |
| --------------------- | ---------------------------------------------------- | ----------------------------------------- |
| **Transport**         | TLS (self-signed or CA-signed cert)                  | HTTPS (standard TLS, no cert file needed) |
| **Credential**        | Macaroon (admin, invoice, readonly, or custom-baked) | `x-api-key` header                        |
| **Credential format** | Binary or hex-encoded file                           | String token                              |
| **Scope**             | Per-macaroon permission caveats                      | Per-environment API key                   |
| **Rotation**          | `lncli bakemacaroon` or `ChangePassword` RPC         | Generate new key in dashboard             |

**Before:**

```bash
curl --cacert tls.cert \
  --header "Grpc-Metadata-macaroon: $(xxd -ps -u -c 1000 admin.macaroon)" \
  https://your-node.voltage.cloud:8080/v1/balance/channels
```

**After:**

```bash
curl 'https://voltageapi.com/v1/organizations/{organization_id}/environments/{environment_id}/payments' \
  --header 'x-api-key: your-api-key'
```

### Concept Mapping Reference

This table maps every common LND operation to its Payments API equivalent.

| LND Method                     | LND REST Path                                               | Payments API Equivalent                                   | Notes                                                                                                                                         |
| ------------------------------ | ----------------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `AddInvoice`                   | `POST /v1/invoices`                                         | `POST /payments` with `payment_kind`                      | Use `payment_kind: "bolt11"` for Lightning. Response is 202; fetch with GET to retrieve the invoice string.                                   |
| `SendPaymentV2`                | `POST /v2/router/send`                                      | `POST /payments` with `type: "bolt11"`                    | No route hints or fee limits to manage beyond `max_fee`. Status via GET or webhooks instead of streaming.                                     |
| `DecodePayReq`                 | `GET /v1/payreq/{pay_req}`                                  | Not applicable                                            | The Payments API handles invoice parsing internally. If you need to inspect an invoice before paying, decode it client-side or use a library. |
| `NewAddress`                   | `POST /v1/newaddress`                                       | `POST /payments` with `payment_kind: "onchain"` (receive) | Creates a receive payment that includes a Bitcoin address in the response.                                                                    |
| `SendCoins`                    | `POST /v1/transactions`                                     | `POST /payments` with `type: "onchain"`                   | Amount in msats via the Amount object, not satoshis.                                                                                          |
| `SubscribeInvoices`            | `GET /v1/invoices/subscribe`                                | Webhooks + reconciliation loop                            | See "The Big Shift" below. Subscribe to `receive.completed`, `receive.expired`, etc.                                                          |
| `LookupInvoice`                | `GET /v1/invoice/{r_hash}`                                  | `GET /payments/{payment_id}`                              | Look up by your `payment_id` (UUID), not by payment hash.                                                                                     |
| `ListPayments`                 | `GET /v1/payments`                                          | `GET /payments?direction=send`                            | Supports filtering by `statuses[]`, `wallet_id`, pagination via `offset`/`limit`.                                                             |
| `ListInvoices`                 | `GET /v1/invoices`                                          | `GET /payments?direction=receive`                         | Same endpoint, different `direction` filter.                                                                                                  |
| `WalletBalance`                | `GET /v1/balance/blockchain`                                | `GET /wallets/{wallet_id}`                                | Returns wallet balance. For node-backed setups you can still check on-chain balance via LND directly.                                         |
| `ChannelBalance`               | `GET /v1/balance/channels`                                  | `GET /wallets/{wallet_id}`                                | The Payments API exposes a single wallet balance. Channel-level detail is available through LND directly (node-backed only).                  |
| `ListChannels`                 | `GET /v1/channels`                                          | Not applicable                                            | Channel management stays on LND (node-backed) or is not applicable (credit-backed).                                                           |
| `OpenChannel` / `CloseChannel` | `POST /v1/channels` / `DELETE /v1/channels/{channel_point}` | Not applicable                                            | Same as above.                                                                                                                                |
| `GetInfo`                      | `GET /v1/getinfo`                                           | Not applicable                                            | Node-level info (pubkey, alias, sync status) is an LND concern, not a Payments API concern.                                                   |

### The Big Shift: Streaming → Webhooks + Polling

This is the biggest architectural change in the migration. LND uses long-lived gRPC streams; the Payments API uses webhooks backed by a reconciliation loop.

**How monitoring works today (direct LND):**

You open a gRPC stream (`SubscribeInvoices`, `SubscribeChannelEvents`, etc.) and process events as they arrive. If the stream drops, you reconnect and may need to replay from an index.

**How monitoring works with the Payments API:**

1. **Webhooks** (primary) — Voltage sends HTTP POST requests to your endpoint for each event (`send.succeeded`, `receive.completed`, etc.)
2. **Reconciliation loop** (safety net) — A cron job periodically lists payments updated since your last checkpoint, catching anything your webhook handler missed
3. **Ad-hoc polling** (optional) — `GET /payments/{payment_id}` for on-demand status checks

**Recommended architecture:**

- A **webhook handler** that receives events and upserts payments into your database keyed on `payment.id`
- A **reconciliation cron** (every 1–5 minutes) that lists recently updated payments and upserts them the same way

Both paths write to the same table using the same idempotent upsert logic. The webhook gives you real-time speed; the recon loop guarantees completeness.

|                       | **Before (gRPC streaming)**                          | **After (Webhooks + Recon)**                                                                                                          |
| --------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **Connection model**  | Persistent bidirectional stream                      | Stateless HTTP POSTs to your endpoint                                                                                                 |
| **Missed events**     | Must track stream index and replay on reconnect      | Reconciliation loop catches missed deliveries automatically                                                                           |
| **Latency**           | Sub-second (same connection)                         | Sub-second (webhook) to minutes (reconciliation loop)                                                                                 |
| **Reliability**       | Depends on stream stability and your reconnect logic | Voltage retries failed deliveries; recon loop as safety net                                                                           |
| **Auth**              | Macaroon on the stream                               | `shared_secret` for webhook signature verification; x-api-key for reconciliation polling<br />\`\`                                    |
| **Event granularity** | Per-invoice or per-payment updates                   | Per-event types: `send.succeeded`, `send.failed`, `receive.generated`, `receive.completed`, `receive.expired`, `receive.failed`, etc. |

For webhook setup details, signature verification, and reconciliation loop implementation, see the [Webhooks](docId\:PdeH_4ET9AqAWca5q91nA) guide.

### Payment Status Changes

The Payments API uses different status names than LND.

**Send payments:**

| LND Status  | Payments API Status | Meaning                     |
| ----------- | ------------------- | --------------------------- |
| `IN_FLIGHT` | `sending`           | Payment is being routed     |
| `SUCCEEDED` | `completed`         | Payment delivered           |
| `FAILED`    | `failed`            | Routing failed or timed out |

**Receive payments:**

| LND Status          | Payments API Status | Meaning                             |
| ------------------- | ------------------- | ----------------------------------- |
| *(invoice created)* | `generating`        | Invoice/address being created       |
| `OPEN` / `ACCEPTED` | `receiving`         | Invoice issued, waiting for payment |
| `SETTLED`           | `completed`         | Payment received and credited       |
| `CANCELED`          | `expired`           | Invoice expired unpaid              |
| *(error)*           | `failed`            | Generation or receipt failed        |

### Amount Handling

LND works in satoshis (and sometimes millisatoshis as separate fields). The Payments API uses an `Amount` object everywhere:

```json
{
  "amount": 150000,
  "currency": "btc",
  "unit": "msats"
}
```

- All BTC amounts use **millisatoshis** as the base unit
- USD amounts use **cents** as the base unit (`"currency": "usd", "unit": "cents"`)
- Legacy fields `amount_msats` and `amount_sats` are accepted but deprecated — use the Amount object

**Conversion reference:** 1 sat = 1,000 msats. If your LND code sends 15,000 sats, the equivalent Payments API amount is `15000000` msats.

### What You Can Remove

After migrating, you can delete the following from your codebase and infrastructure:

- **TLS certificate management** — no cert files to load, rotate, or distribute
- **Macaroon loading and injection** — no binary token files; just an API key string
- **gRPC / protobuf setup** — no `.proto` files, code generation, or gRPC client libraries
- **Route finding and path optimization** — the Payments API handles routing
- **Invoice decoding** — the API parses invoices internally
- **Channel rebalancing logic** — handled by Voltage (node-backed with Support Macaroon) or not applicable (credit-backed)
- **Fee estimation** — `max_fee` defaults are sensible; no manual fee rate lookups needed
- **Stream reconnection logic** — webhooks replace gRPC streams

## Next Steps

Pick your path and follow the step-by-step guide:

- [Migrating to Node Backed Payments](docId\:BrkO-WDXk1DiOPuqWmFFs) — keep your node, use the API
- [Migrating to Credit Backed Payments](docId\:PpMDfT3_OtGSnxZ7ai6Ry) — drop your node, use a Line of Credit
