Migration Guide
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 PaymentsMigrating to Node Backed Payments — keep your node, use the API
- Migrating to Credit Backed PaymentsMigrating to Credit Backed Payments — 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:
curl --cacert tls.cert \
--header "Grpc-Metadata-macaroon: $(xxd -ps -u -c 1000 admin.macaroon)" \
https://your-node.voltage.cloud:8080/v1/balance/channelsAfter:
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:
- Webhooks (primary) — Voltage sends HTTP POST requests to your endpoint for each event (send.succeeded, receive.completed, etc.)
- Reconciliation loop (safety net) — A cron job periodically lists payments updated since your last checkpoint, catching anything your webhook handler missed
- 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 `` |
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 WebhooksWebhooks 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:
{
"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 PaymentsMigrating to Node Backed Payments — keep your node, use the API
- Migrating to Credit Backed PaymentsMigrating to Credit Backed Payments — drop your node, use a Line of Credit