Migrating to Node Backed Payments
Keep your Voltage LND node. Route payments through the Voltage Payments API instead of calling LND directly.
Prerequisites
- An existing Voltage LND node (running and synced)
- An Enterprise plan (node-backed setup requires Enterprise)
- You've read Migration GuideMigration Guide (auth changes, concept mapping, status mapping, amount handling)
Migration Steps
- Set up Payments environment and wallet
- Confirm node operations ownership
- Set up webhooks
- Migrate sending
- Migrate receiving
- Implement reconciliation loop
- Parallel run
- Cut over
Step 1: Set Up Payments Environment and Wallet
Follow the Node-backed SetupNode-backed Setup guide to:
- Create a Payments environment in the Voltage dashboard
- Create a wallet linked to your existing node (check "I want to use a team infrastructure node" and select your node)
- Generate an API key for the environment
After this step you'll have three values you need for every API call:
- organization_id
- environment_id
- wallet_id
And one credential:
- Your x-api-key
Step 2: Confirm Node Operations Ownership
Before migration, confirm who owns liquidity operations, channel changes, credential custody, and escalation for the node.
- Review the current node access model → operating owner → approved workflow
- Click the least-privilege credential approved for the workflow
- Record the credential owner, scope, storage location, and removal plan.
Limit access to the actions approved for the operating owner — review and remove the access when it is no longer needed.
For details on macaroon types and security, see the Node SecurityNode Security security guide.
Step 3: Set Up Webhooks
Replace your gRPC stream listeners with webhooks.
1. Create a publicly accessible HTTPS endpoint on your server to receive webhook POST requests.
2. Register the webhook:
curl "https://voltageapi.com/v1/organizations/{organization_id}/environments/{environment_id}/webhooks" \
--request POST \
--header "Content-Type: application/json" \
--header "x-api-key: your-api-key" \
--data '{
"id": "b0fc9829-f139-4035-bb14-4a4b6cd58f0e",
"organization_id": "{organization_id}",
"environment_id": "{environment_id}",
"url": "https://your-domain.com/webhook",
"name": "Migration Webhook",
"events": [
{ "send": "succeeded" },
{ "send": "failed" },
{ "receive": "generated" },
{ "receive": "completed" },
{ "receive": "expired" },
{ "receive": "failed" }
]
}'3. Save the shared_secret from the response — it's only returned once.
4. Implement signature verification in your webhook handler:
const crypto = require("crypto");
function verifyWebhookSignature(payload, signature, timestamp, sharedSecret) {
const message = `${payload}.${timestamp}`;
const hmac = crypto.createHmac("sha256", sharedSecret);
hmac.update(message);
const expectedSignature = hmac.digest("base64");
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(signature)
);
}Read x-voltage-signature, x-voltage-timestamp, and x-voltage-event from the request headers.
5. Test the webhook:
curl "https://voltageapi.com/v1/organizations/{organization_id}/environments/{environment_id}/webhooks/{webhook_id}/test" \
--request POST \
--header "Content-Type: application/json" \
--header "x-api-key: your-api-key" \
--data '{
"delivery_id": "123e4567-e89b-12d3-a456-426614174002",
"payload": {
"type": "test",
"detail": {
"event": "created",
"data": "test"
}
}
}'For full webhook documentation, see the WebhooksWebhooks guide.
Step 4: Migrate Sending
Lightning Payment
Before (LND REST):
curl --cacert tls.cert \
--header "Grpc-Metadata-macaroon: $(xxd -ps -u -c 1000 admin.macaroon)" \
https://your-node.voltage.cloud:8080/v2/router/send \
--request POST \
--data '{
"payment_request": "lntbs1500n1p...",
"timeout_seconds": 60,
"fee_limit_msat": 1000,
"no_inflight_updates": true
}'After (Payments API):
curl 'https://voltageapi.com/v1/organizations/{organization_id}/environments/{environment_id}/payments' \
--request POST \
--header 'x-api-key: your-api-key' \
--header 'Content-Type: application/json' \
--data '{
"id": "68d00852-8dd8-4c71-94d2-91c84695da78",
"wallet_id": "{wallet_id}",
"currency": "btc",
"type": "bolt11",
"data": {
"payment_request": "lntbs1500n1p...",
"amount": {
"currency": "btc",
"amount": 150000,
"unit": "msats"
},
"max_fee": {
"currency": "btc",
"amount": 1000,
"unit": "msats"
}
}
}'Key differences:
- You generate the id (UUID) — this is your idempotency key
- Amount uses the Amount object (msats), not a bare integer
- max_fee replaces fee_limit_msat — same concept, structured format
- No timeout_seconds — the API manages routing timeouts
- Response is 202; monitor status via GET /payments/{payment_id} or webhooks
On-chain Payment
Before (LND REST):
curl --cacert tls.cert \
--header "Grpc-Metadata-macaroon: $(xxd -ps -u -c 1000 admin.macaroon)" \
https://your-node.voltage.cloud:8080/v1/transactions \
--request POST \
--data '{
"addr": "tb1pzkhtj4ld86g9c49du5yagnncfrm0s489t76vmrwmt2ecxfnf7spsvjte49",
"amount": 15000,
"sat_per_vbyte": 10
}'After (Payments API):
curl 'https://voltageapi.com/v1/organizations/{organization_id}/environments/{environment_id}/payments' \
--request POST \
--header 'x-api-key: your-api-key' \
--header 'Content-Type: application/json' \
--data '{
"id": "2ec1e783-19b4-4c10-8181-66336a6232bd",
"wallet_id": "{wallet_id}",
"currency": "btc",
"type": "onchain",
"data": {
"address": "tb1pzkhtj4ld86g9c49du5yagnncfrm0s489t76vmrwmt2ecxfnf7spsvjte49",
"amount": {
"currency": "btc",
"amount": 15000000,
"unit": "msats"
},
"max_fee": {
"currency": "btc",
"amount": 1000000,
"unit": "msats"
},
"description": "test payment"
}
}'Key differences:
- addr → data.address
- amount in sats → data.amount in msats (15,000 sats = 15,000,000 msats)
- sat_per_vbyte → data.max_fee (max fee budget, not fee rate)
Step 5: Migrate Receiving
Lightning Invoice
Before (LND REST):
curl --cacert tls.cert \
--header "Grpc-Metadata-macaroon: $(xxd -ps -u -c 1000 admin.macaroon)" \
https://your-node.voltage.cloud:8080/v1/invoices \
--request POST \
--data '{
"value_msat": 150000,
"memo": "test payment",
"expiry": 3600
}'After (Payments API) — Step 1: Create the receive payment:
curl 'https://voltageapi.com/v1/organizations/{organization_id}/environments/{environment_id}/payments' \
--request POST \
--header 'x-api-key: your-api-key' \
--header 'Content-Type: application/json' \
--data '{
"id": "11ca843c-bdaa-44b6-965a-39ac550fcef7",
"wallet_id": "{wallet_id}",
"currency": "btc",
"payment_kind": "bolt11",
"amount": {
"amount": 150000,
"currency": "btc",
"unit": "msats"
},
"description": "test payment",
"expiration": 3600
}'After — Step 2: Fetch the payment to get the invoice string:
curl 'https://voltageapi.com/v1/organizations/{organization_id}/environments/{environment_id}/payments/11ca843c-bdaa-44b6-965a-39ac550fcef7' \
--header 'x-api-key: your-api-key'The data.payment_request field in the response contains the BOLT11 invoice string to give to the payer.
Key differences:
- Receiving is a two-step flow: create (202) → fetch (200 with invoice)
- Send uses type; receive uses payment_kind — same values (bolt11, onchain, bip21)
- value_msat → amount object
- memo → description in the request (returned as memo in the response)
- LND returns the invoice immediately; Payments API returns 202 and you fetch separately
On-chain Receive
Before (LND REST):
curl --cacert tls.cert \
--header "Grpc-Metadata-macaroon: $(xxd -ps -u -c 1000 admin.macaroon)" \
https://your-node.voltage.cloud:8080/v1/newaddress?type=TAPROOT_PUBKEYAfter (Payments API):
curl 'https://voltageapi.com/v1/organizations/{organization_id}/environments/{environment_id}/payments' \
--request POST \
--header 'x-api-key: your-api-key' \
--header 'Content-Type: application/json' \
--data '{
"id": "11ca843c-bdaa-44b6-965a-39ac550fcef8",
"wallet_id": "{wallet_id}",
"currency": "btc",
"payment_kind": "onchain",
"amount": {
"amount": 150000000,
"currency": "btc",
"unit": "msats"
},
"description": "on-chain deposit"
}'Fetch the payment to get data.address. The API tracks confirmations via data.receipts.
Step 6: Implement Reconciliation Loop
Set up a cron job (every 1–5 minutes) as a safety net alongside your webhook handler. The full implementation is documented in the WebhooksWebhooks guide under "Reconciliation Loop."
In short:
- Store a last_reconciled_at timestamp
- Query GET /payments?start_date={last_reconciled_at - 10 min}&end_date={now}&sort_key=updated_at&sort_order=ASC
- Page through all results
- Upsert each payment into your database keyed on payment.id
- Advance last_reconciled_at
The overlap window and idempotent upserts make this safe to run as frequently as you want.
Step 7: Parallel Run
Run both your existing LND integration and the new Payments API integration side by side. Verify that the new path works correctly before cutting over.
Verification checklist:
Send a Lightning payment via the Payments API → status reaches completed
Receive a Lightning payment via the Payments API → invoice generated, payment completed
Send an on-chain payment via the Payments API → status reaches completed
Receive an on-chain payment via the Payments API → address generated, payment completed
Webhooks fire for all send and receive events
Reconciliation loop catches all payments (cross-reference with webhook-delivered payments)
Wallet balance in Payments API matches expected values
Step 8: Cut Over
Once your parallel run is verified:
- Disable your old LND integration — stop calling LND REST/gRPC endpoints for payments
- Remove old code — TLS cert loading, macaroon injection, gRPC client setup, stream reconnection logic (see "What You Can Remove" in the Migration Guideparent guide)
- Keep node management access — you still need ThunderHub and/or lncli for:
- Channel opens and closes
- Liquidity and treasury management
- Node unlocking after restarts or updates
- Seed phrase and SCB backup access
What You Still Manage
Even after migrating to the Payments API, the following remain your responsibility on a node-backed setup:
- Node uptime — your node must stay running and synced
- Channels — open, close, and rebalance as needed (Voltage assists via the approved operating workflow)
- Liquidity / treasury — keep capacity above minimum thresholds per your treasury management strategy with Voltage
- Seed phrase + SCB backups — maintain secure offline copies
- On-chain deposits — fund your node when Voltage notifies you that capacity is below the minimum
- Node unlocking — unlock with your password after every restart or update