What you can receive
Lightning receives BTC, not stablecoins or on-chain address transfers. Each checkout request has its own amount, payment hash and expiry. The customer pays the complete Lightning invoice.
Bitcoin on-chain paymentsSet up your store
In Settings → Chain connections, add LND over HTTPS or Nostr Wallet Connect over WSS, then enable that connection in the store. The current connection policy requires publicly reachable endpoints; a LAN-only or Tor-only address will not work directly.
Credentials and custody
Use restricted receiving credentials, never an LND admin or spending macaroon. NWC describes the connection, not custody: check whether the selected wallet holds the keys for you.
Where your payments arrive
The QR contains a BOLT11 Lightning invoice, not a Bitcoin address. Never send an on-chain transaction to pay it. Create a new invoice if the request has expired; its deadline cannot simply be extended.
When is the payment complete?
Wholly Crypto verifies settlement reported by the connected wallet. An open invoice or held HTLC is not payment. No block confirmations or public transaction explorer link apply to a Lightning receipt.
Move funds when you are ready
Use the external wallet or node for outgoing payments, channel liquidity and backups. Wholly Crypto’s on-chain sweep does not move Lightning balances. A project wallet export does not back up that external wallet.
Lightning wallet guidePayment troubleshooting
If payment cannot route, check incoming liquidity, wallet availability and request expiry. A healthy API is not a routing guarantee. After an uncertain payment, verify settlement before asking the customer to retry.
Fees and verification
The default processing fee is 1% of the invoice’s fiat value, paid from separate credits. Your assigned rate applies. Network fees and server costs are separate.
Public providers can have outages or rate limits. A submitted transaction or return redirect is not proof of payment. Use the verified invoice status and signed notifications.
Processing credits API, IPN & webhooksRates calculator
Convert an amount
An estimate, not a payment quote. Network fees, exchange fees and merchant spreads are not included.
Where do the rates come from?
Crypto estimates use cached public Kraken prices, with Binance where available. Some Binance prices use USDT as a USD reference. Fiat rates come from Frankfurter and are daily reference rates, not live trading quotes.
The snapshot refreshes automatically. Check the source dates below your result. Your entered amount stays in your browser. Asset listings do not guarantee payment support.
Kraken · Binance · Frankfurter · Asset names & icons: CoinGecko
Create an invoice with the API
Run these examples on your server. Replace the API host, project ID and store ID, and keep the credential private. Amounts are decimal strings.
Enable the method in the store first. Selection narrows enabled methods; it does not add assets. If nothing matches, store defaults apply. Check the returned methods before presenting checkout.
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new invoice.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: order-1042-attempt-1' \
--header 'Content-Type: application/json' \
--data-raw '{
"amount": "25.00",
"currency": "EUR",
"order_id": "order-1042",
"payment_methods": [
{
"chain_slug": "bitcoin",
"payment_rail": "lightning"
}
]
}'// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new invoice.
const body = `{
"amount": "25.00",
"currency": "EUR",
"order_id": "order-1042",
"payment_methods": [
{
"chain_slug": "bitcoin",
"payment_rail": "lightning"
}
]
}`;
const response = await fetch("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new invoice.
$body = <<<'JSON'
{
"amount": "25.00",
"currency": "EUR",
"order_id": "order-1042",
"payment_methods": [
{
"chain_slug": "bitcoin",
"payment_rail": "lightning"
}
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: order-1042-attempt-1", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new invoice.
headers = {
"Idempotency-Key": "order-1042-attempt-1",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"amount": "25.00",
"currency": "EUR",
"order_id": "order-1042",
"payment_methods": [
{
"chain_slug": "bitcoin",
"payment_rail": "lightning"
}
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/projects/YOUR_PROJECT_ID/stores/YOUR_STORE_ID/invoices",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))