# 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

- 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](/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](/developer-guide#generating-api-key). Keep the key in your terminal or secret manager.

## 1. Configure your terminal

Run these examples in Bash. Create a new working directory for the request and response files:

```bash
mkdir voltage-quickstart
cd voltage-quickstart
umask 077
```

Replace the three ID placeholders with values from your staging environment and receiving wallet:

```bash
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_KEY
printf '\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

Read the wallet with your environment key:

```bash
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.json
```

Expect **HTTP 200** and a JSON wallet object. Check that the IDs match your selection, `active` is `true`, and `network` is `mutinynet`:

```bash
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.json
```

Continue 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

Generate a payment ID once and save it before submitting the request:

```bash
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.json
```

BTC 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:

```bash
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

```bash
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.json
```

Expect **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:

```bash
jq -er 'select(.status == "receiving") | .data.payment_request | select(type == "string" and length > 0)' payment.json
```

Copy 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

1. Open **Quickstart payer** in the same staging environment.
2. Confirm that it uses **Mutinynet Bitcoin** and has enough available test funds or credit.
3. Select **Send** and paste the invoice from step 4.
4. Review the destination, invoice amount, and fees.
5. 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

Read the **receiving** payment again using the same ID:

```bash
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.json
```

Expect `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:

```bash
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.json
```

When 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

| 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

Use [Receiving](/receiving) for other payment types and [Webhooks](/webhooks) for event-driven confirmation. Keep API keys on your server when you build an application.

Use the [API reference](https://voltageapi.com/v1/docs) and [OpenAPI contract](https://voltageapi.com/v1/openapi/docs.json) for endpoint schemas and permission requirements.
