Sending (USD Line of Credit)
Sending Payments with USD Line of Credit
This guide explains how to send Lightning Network and on-chain Bitcoin payments from a USD-denominated wallet using the Voltage Payments API. USD wallets leverage a line of credit to convert USD to BTC at the time of payment.
For Development testing, choose Mutinynet USD. Payments move over Bitcoin or Lightning while the wallet and line of credit are denominated in USD. Mutinynet Bitcoin and Voltage Cash are not the correct wallets for this workflow.
Prerequisites
- A Voltage account with an active USD wallet (credit-backed wallet with an associated USD line of credit)
- An API key with access to the environment (from the "API Keys" page in your dashboard)
- The Quotes API enabled, and a valid line_of_credit_id for your USD wallet's line of credit
- The target Lightning invoice or Bitcoin address you intend to pay
Workflow Overview
Sending from a USD wallet involves three steps:
- Determine the BTC amount – If paying a Lightning invoice, decode it to extract the payment amount in millisatoshis. For on-chain payments where you already know the amount, skip to Step 2.
- Obtain a conversion quote – Use the POST /quotes endpoint to get a quote for converting USD to BTC. This locks in an exchange rate and returns a quote_id.
- Create the payment – Call the POST /payments endpoint with your USD wallet, including the quote_id and payment details.
Step 1: Determine the BTC Amount from a Lightning Invoice
To create a quote, you need the BTC amount in millisatoshis (msats). When paying a Lightning invoice, the amount is encoded in the invoice string itself and must be extracted before you can request a quote.
The Voltage API does not currently provide an invoice decoding endpoint. You will need to decode the invoice client-side using a BOLT-11 decoding library.
JavaScript / TypeScript
Using light-bolt11-decoder:
npm install light-bolt11-decoderimport { decode } from 'light-bolt11-decoder';
const invoice = 'lnbc1500n1p...';
const decoded = decode(invoice);
const amountSection = decoded.sections.find(s => s.name === 'amount');
const amountMsats = amountSection ? Number(amountSection.value) : null;
if (!amountMsats) {
throw new Error('Invoice does not contain an amount');
}
console.log(`Amount: ${amountMsats} msats`);
// Use amountMsats as the "amount" in your quote request (Step 2)Python
Using bolt11:
pip install bolt11import bolt11
invoice = "lnbc1500n1p..."
decoded = bolt11.decode(invoice)
amount_msats = decoded.amount_msat
if not amount_msats:
raise ValueError("Invoice does not contain an amount")
print(f"Amount: {amount_msats} msats")
# Use amount_msats as the "amount" in your quote request (Step 2)For on-chain or BIP21 payments where you already know the amount you want to send, you can skip this step and go directly to Step 2.
Step 2: Get a Quote for USD→BTC Conversion
Before creating a payment from a USD wallet, you must get a quote to convert the payment amount into BTC. Use the amount you decoded in Step 1 (or the amount you already know for on-chain payments).
Endpoint
POST https://voltageapi.com/v1/organizations/{organization_id}/environments/{environment_id}/quotesHeaders
x-api-key: your-api-key
Content-Type: application/jsonExample Request
curl 'https://voltageapi.com/v1/organizations/{organization_id}/environments/{environment_id}/quotes' \
--request POST \
--header 'x-api-key: your-api-key' \
--header 'Content-Type: application/json' \
--data '{
"id": "87654321-4321-8765-cba9-fed987654321",
"line_of_credit_id": "{line_of_credit_id}",
"network": "mainnet",
"amount": {
"amount": 150000,
"currency": "btc",
"unit": "msats"
},
"to": "usd"
}'Request Fields
- id – A client-generated UUID for the quote (for idempotency)
- line_of_credit_id – Your USD line of credit ID
- network – The Bitcoin network ("mainnet", "testnet", "signet", or "mutinynet")
- amount – The BTC amount you need to send (in msats). When paying a Lightning invoice, this is the amount decoded in Step 1.
- to – Target currency for conversion ("usd")
The API will respond with a quote object containing an exchange rate and a quote_id. Use that quote_id in Step 3.
Quotes are time-limited. Use the quote promptly before it expires.
Step 3: Create a Payment from the USD Wallet
With a valid quote in hand, create the payment using the POST /payments endpoint.
Endpoint
POST https://voltageapi.com/v1/organizations/{organization_id}/environments/{environment_id}/paymentsHeaders
x-api-key: your-api-key
Content-Type: application/json3.1 Send a Lightning (Bolt11) Payment
To pay a Lightning invoice from a USD wallet, provide the invoice in the payment_request field.
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": "{usd_wallet_id}",
"currency": "usd",
"quote_id": "12345678-1234-5678-9abc-def012345678",
"type": "bolt11",
"data": {
"payment_request": "lnbc1500n1p...",
"amount": {
"currency": "btc",
"amount": 150000,
"unit": "msats"
},
"max_fee": {
"currency": "btc",
"amount": 1000,
"unit": "msats"
}
}
}'Request Fields
- id – A client-generated UUID for the payment (for idempotency)
- wallet_id – The UUID of your USD wallet
- currency – "usd" (the wallet's currency)
- quote_id – The UUID of the quote obtained in Step 2 (required for USD wallets)
- type – "bolt11" for a Lightning payment
- data.payment_request – The BOLT-11 Lightning invoice string (cannot be empty)
- data.amount – Optional BTC amount object. Required if the invoice has no amount encoded; must match if the invoice has a fixed amount
- data.max_fee – Optional maximum routing fee in BTC (defaults to 1% of payment or 1,000 msats, whichever is greater)
3.2 Send an On-Chain Bitcoin Payment
To send BTC on-chain from a USD wallet, provide the Bitcoin address and amount.
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": "{usd_wallet_id}",
"currency": "usd",
"quote_id": "12345678-1234-5678-9abc-def012345678",
"type": "onchain",
"data": {
"address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"amount": {
"currency": "btc",
"amount": 15000000,
"unit": "msats"
},
"max_fee": {
"currency": "btc",
"amount": 1000000,
"unit": "msats"
},
"description": "Payment for services"
}
}'Request Fields
- type – "onchain" for an on-chain BTC payment
- data.address – The recipient's Bitcoin address
- data.amount – The amount to send in BTC (msats). Example: 15,000,000 msats = 15,000 sats
- data.max_fee – Optional maximum miner fee in msats
- data.description – Optional memo for your records
3.3 Send a Unified BIP21 Payment
A BIP21 URI can include both an on-chain address and an embedded Lightning invoice. The API will parse the URI and execute the payment via Lightning or on-chain as appropriate.
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": "3e84b6c5-5bbe-4e0f-9fb3-f1198330f6fa",
"wallet_id": "{usd_wallet_id}",
"currency": "usd",
"quote_id": "12345678-1234-5678-9abc-def012345678",
"type": "bip21",
"data": {
"address": "bitcoin:bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh?label=Example%20Store&message=Payment%20for%20services&lightning=lnbc1500...qpz",
"payment_request": "lnbc1500n1p...",
"amount": {
"currency": "btc",
"amount": 150000,
"unit": "msats"
},
"max_fee": {
"currency": "btc",
"amount": 1000,
"unit": "msats"
},
"description": "Payment for services"
}
}'Request Fields
- type – "bip21" for a unified Bitcoin payment
- data.address – The BIP21 URI string (required)
- data.payment_request – Optional Lightning invoice if you have it separately
- data.amount – Optional BTC amount object (can be omitted if URI contains amount)
- data.max_fee – Optional max Lightning fee in msats
- data.description – Optional memo (independent of any invoice memo)
Monitoring Payment Status
After initiating a payment, monitor its status using:
GET https://voltageapi.com/v1/organizations/{organization_id}/environments/{environment_id}/payments/{payment_id}Example
curl 'https://voltageapi.com/v1/organizations/{organization_id}/environments/{environment_id}/payments/{payment_id}' \
--header 'x-api-key: your-api-key'Payment States
- sending – Payment is in progress
- completed – Payment was successful and funds have been delivered
- failed – Payment failed (check the error field for details)
Response Fields for USD Payments
For completed USD payments, the response includes:
- data.market_quote – The exchange rate used for USD/BTC conversion
- data.amount – The amount in USD that was debited from your wallet
Error Handling
HTTP Status Codes
- 200 – Success
- 400 – Invalid request (missing fields, expired/invalid quote, etc.)
- 403 – Authentication or permissions error
- 404 – Resource not found
- 500 – Server error
Common Errors
- Missing quote_id – USD wallet payments require a valid quote_id
- Expired quote – Quotes are time-limited; obtain a new one
- Invalid invoice – The Lightning invoice is malformed or expired
- Insufficient balance – Your USD wallet doesn't have enough funds
When a payment fails, the status will be "failed" and the error object will contain details about the failure reason.