Developer Guide
Migrating to Node Backed Payments
Keep your Voltage LND node. Route payments through the Voltage Payments API instead of calling LND directly.
Prerequisites
Section titled “Prerequisites”- An existing Voltage LND node (running and synced)
- An Enterprise plan (node-backed setup requires Enterprise)
- You’ve read Migration Guide (auth changes, concept mapping, status mapping, amount handling)
Migration Steps
Section titled “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
Section titled “Step 1: Set Up Payments Environment and Wallet”Follow the Node-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_idenvironment_idwallet_id
And one credential:
- Your
x-api-key
Step 2: Confirm Node Operations Ownership
Section titled “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 Security security guide.
Step 3: Set Up Webhooks
Section titled “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 Webhooks guide.
Step 4: Migrate Sending
Section titled “Step 4: Migrate Sending”Lightning Payment
Section titled “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
Amountobject (msats), not a bare integer max_feereplacesfee_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
Section titled “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.addressamountin sats →data.amountin msats (15,000 sats = 15,000,000 msats)sat_per_vbyte→data.max_fee(max fee budget, not fee rate)
Step 5: Migrate Receiving
Section titled “Step 5: Migrate Receiving”Lightning Invoice
Section titled “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 usespayment_kind— same values (bolt11,onchain,bip21) value_msat→amountobjectmemo→descriptionin the request (returned asmemoin the response)- LND returns the invoice immediately; Payments API returns 202 and you fetch separately
On-chain Receive
Section titled “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
Section titled “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 Webhooks guide under “Reconciliation Loop.”
In short:
- Store a
last_reconciled_attimestamp - 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
Section titled “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
Section titled “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 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
Section titled “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