Start here
Make your first test payment
Receive your first Lightning payment through the Voltage API. You will create a 150-sat invoice, pay it with test funds, and verify completion.
This tutorial uses Mutinynet Bitcoin, a Development wallet with no real-world value. You need two wallets: one to receive and another to pay.
Before you start
Section titled “Before you start”- A Voltage account with access to a staging environment.
- Two Mutinynet Bitcoin Development wallets in that environment: a receiver and a payer.
- Enough available test balance or test credit in the payer to cover the invoice and fees.
- An environment API key with READ and RECEIVE access. The dashboard payer also needs permission to send.
- A terminal with Bash, curl 7.76 or later, jq, and
uuidgen.
Follow the Wallet Setup Guide to create the environment and wallets. Name them Quickstart receiver and Quickstart payer so you can distinguish them.
Check the payer’s available funds in its wallet overview. A wallet name or credit limit alone does not establish available funding. If you need test funding, contact your team’s Voltage administrator before continuing.
Create the API key from the staging environment’s API Keys page. See Generate an API key. Keep the key in your terminal or secret manager.
1. Configure your terminal
Section titled “1. Configure your terminal”Run these examples in Bash. Create a new working directory for the request and response files:
mkdir voltage-quickstartcd voltage-quickstartumask 077Replace the three ID placeholders with values from your staging environment and receiving wallet:
API_BASE_URL='https://voltageapi.com/v1'ORGANIZATION_ID='<organization-uuid>'ENVIRONMENT_ID='<staging-environment-uuid>'WALLET_ID='<receiving-wallet-uuid>'
read -r -s -p 'Environment API key: ' VOLTAGE_API_KEYprintf '\n'The hidden prompt keeps the key out of shell history. Run the remaining commands in this terminal so they can use these variables.
2. Check the receiving wallet
Section titled “2. Check the receiving wallet”Read the wallet with your environment key:
curl --silent --show-error --fail-with-body --max-time 15 \ "$API_BASE_URL/organizations/$ORGANIZATION_ID/wallets/$WALLET_ID" \ --header "x-api-key: $VOLTAGE_API_KEY" \ --output wallet.json
jq '{id, organization_id, environment_id, active, network, balances}' wallet.jsonExpect HTTP 200 and a JSON wallet object. Check that the IDs match your selection, active is true, and network is mutinynet:
jq -e \ --arg wallet "$WALLET_ID" \ --arg org "$ORGANIZATION_ID" \ --arg env "$ENVIRONMENT_ID" \ '.id == $wallet and .organization_id == $org and .environment_id == $env and .active == true and .network == "mutinynet"' wallet.jsonContinue when this prints true. Confirm Mutinynet Bitcoin in the dashboard; this tutorial uses a BTC wallet. Existing balance entries show currency: "btc"; a new wallet can have an empty balances array.
3. Create a 150-sat invoice
Section titled “3. Create a 150-sat invoice”Generate a payment ID once and save it before submitting the request:
PAYMENT_ID=$(uuidgen | tr '[:upper:]' '[:lower:]')printf '%s\n' "$PAYMENT_ID" > payment-id.txt
jq -n \ --arg id "$PAYMENT_ID" \ --arg wallet "$WALLET_ID" \ '{ id: $id, wallet_id: $wallet, payment_kind: "bolt11", amount: {currency: "btc", amount: 150000}, description: "My first Voltage test payment", expiration: 3600 }' > receive-request.jsonBTC amounts use millisatoshis: 150000 msats equals 150 sats. This requests 150 sats of principal. Voltage adds any configured receive processing fee to the payer-facing invoice.
Submit the saved request:
curl --silent --show-error --fail-with-body --max-time 15 \ --request POST \ "$API_BASE_URL/organizations/$ORGANIZATION_ID/environments/$ENVIRONMENT_ID/payments" \ --header "x-api-key: $VOLTAGE_API_KEY" \ --header 'content-type: application/json' \ --data-binary @receive-request.json \ --output create-response.json \ --write-out 'HTTP %{http_code}\n'Expect HTTP 202 with an empty response body. Voltage has accepted the request; retrieve the invoice using your saved payment ID.
If the request times out, keep payment-id.txt and check that ID in the next step. Resolve its state before submitting another request.
To resume in a new terminal, set the variables from step 1 again and load the saved ID with PAYMENT_ID=$(cat payment-id.txt).
4. Retrieve the invoice
Section titled “4. Retrieve the invoice”curl --silent --show-error --fail-with-body --max-time 15 \ "$API_BASE_URL/organizations/$ORGANIZATION_ID/environments/$ENVIRONMENT_ID/payments/$PAYMENT_ID" \ --header "x-api-key: $VOLTAGE_API_KEY" \ --output payment.json
jq '{id, direction, type, status, error, invoice: .data.payment_request}' payment.jsonExpect HTTP 200 and a payment object. Wait for status: "receiving" and an invoice in data.payment_request.
If you see generating or a brief 404 after acceptance, wait two seconds and repeat the GET request. Stop after 30 attempts and investigate with the existing payment ID. For failed or expired, follow the troubleshooting table below.
If the payment already shows completed when you resume, skip to step 6. Otherwise, print the ready invoice:
jq -er 'select(.status == "receiving") | .data.payment_request | select(type == "string" and length > 0)' payment.jsonCopy the complete invoice string. A generated invoice means the wallet can receive this payment; the payer still needs to pay it.
5. Pay from the second wallet
Section titled “5. Pay from the second wallet”- Open Quickstart payer in the same staging environment.
- Confirm that it uses Mutinynet Bitcoin and has enough available test funds or credit.
- Select Send and paste the invoice from step 4.
- Review the destination, invoice amount, and fees.
- Submit the test payment and wait for its final result.
Use a separate payer wallet. Mainnet Lightning wallets cannot pay this Mutinynet invoice.
If receive processing fees apply, the invoice amount can exceed 150 sats. Pay the full invoice amount shown in the review.
6. Verify completion through the API
Section titled “6. Verify completion through the API”Read the receiving payment again using the same ID:
curl --silent --show-error --fail-with-body --max-time 15 \ "$API_BASE_URL/organizations/$ORGANIZATION_ID/environments/$ENVIRONMENT_ID/payments/$PAYMENT_ID" \ --header "x-api-key: $VOLTAGE_API_KEY" \ --output payment.json
jq '{id, wallet_id, direction, type, status, requested_amount, error}' payment.jsonExpect status: "completed". If it still shows receiving, wait two seconds and repeat the GET request, up to 30 attempts. Keep the ID if completion takes longer.
Check that the completed payment matches your request:
jq -e \ --arg id "$PAYMENT_ID" \ --arg wallet "$WALLET_ID" \ --arg org "$ORGANIZATION_ID" \ --arg env "$ENVIRONMENT_ID" \ '.id == $id and .wallet_id == $wallet and .organization_id == $org and .environment_id == $env and .direction == "receive" and .type == "bolt11" and .status == "completed" and .error == null and .requested_amount.currency == "btc" and .requested_amount.amount == 150000' payment.jsonWhen this prints true, you have verified the completed 150-sat receive through the authenticated API. You can also find it in the receiver’s All Payments history.
This two-wallet test verifies your integration’s payment lifecycle. Voltage can settle payments between its wallets internally, so it does not prove external Lightning routing.
Troubleshooting
Section titled “Troubleshooting”| What you see | What to do |
|---|---|
400 with missing_credentials, 401, or 403 | Check the environment key, scope, IP allowlist, and READ/RECEIVE permissions. New keys can take about a minute to propagate. Use the matching staging IDs. |
Wallet check prints false | Check the selected wallet, organization, environment, active state, and network. Correct the IDs before creating an invoice. |
Brief 404 after 202 | Repeat the GET after two seconds. If it persists, verify the saved ID and environment; keep the existing payment ID. |
status: "failed" | Inspect the payment’s structured error. Correct the cause before creating a new logical payment with a new ID. |
status: "expired" | Create a new invoice with a new ID and pay it before expiry. |
| Payer lacks funds or credit | Fund the Mutinynet payer through your team’s test setup, or ask your Voltage administrator for test funding. |
| Payer reports completion but receive remains pending | Recheck the receive ID and invoice. Preserve both payment IDs and contact support if the state does not advance. |
| Curl times out or completion exceeds the retry limit | Retrieve the saved ID again later. A timeout does not establish payment failure. |
Next steps
Section titled “Next steps”Use Receiving for other payment types and Webhooks for event-driven confirmation. Keep API keys on your server when you build an application.
Use the API reference and OpenAPI contract for endpoint schemas and permission requirements.