nodana
Get Started / Pay Links

Pay Links

A Pay Link is a shareable URL that opens a hosted Lightning payment page. Your customer scans the QR code with a Lightning wallet or pays through a WebLN-compatible browser wallet. Payments arrive in your selected node's wallet.

You can create a link in the dashboard without writing code, or create links from your website's backend using the API. You need a Nodana account and a ready node; start with Your first node if you haven't created one yet.

Use caseAmountPayment limit
Tips for your work, newsletter, or videosLet the payer chooseUnlimited
Donations to a project or fundraiserLet the payer choose, or set a fixed amountUnlimited
A payment request for a customerSet the amount in sats1
A website checkoutCalculate the amount on your server and create a link for that checkout1
Repeated payments at the same priceSet a fixed amountA number, or unlimited

Share a tip or donation link in your profile, email, or website. For orders, create a separate link for each checkout so you can connect the payment to the right customer and purchase.

  1. Sign in to the dashboard, choose your receiving node, then open Pay Links.
  2. Select the Pay Links tab and click Create link.
  3. Add an optional description. Enter an amount in sats, or leave it blank to let the payer choose.
  4. Choose a display currency. This shows an approximate fiat equivalent; the payment is made in sats.
  5. Set Payment limit to the number of confirmed payments to accept. It defaults to 1. Leave it blank for unlimited payments.
  6. Optionally enter a Return URL for your website, then click Create link.
  7. Copy the link from its actions menu and share it.

The payment page uses the seller name from Account settings. Set this to the name you want your customers to recognise.

The table's Received column shows total confirmed sats received through each link, including links that accept one payment. It starts at 0 sats and updates as payments arrive. Payments shows the number of confirmed payments. Trend 24h shows hourly sats received over the last 24 hours.

When payment is confirmed, the page shows a tick, Payment received, and the amount. If you set a Return URL, the customer can click to return to your website. The destination domain is shown beneath the button.

Create a tip jar with the API

Create a Nodana API key under Developers → API Keys, with Nodes → Read and Nodes → Write permissions. Store it as NODANA_API_KEY in your terminal or backend's secret settings. Keep the key out of browser code.

Replace node-abcd1234 with your receiving node's ID. This request lets each payer choose an amount and accepts unlimited payments:

curl "https://api.nodana.io/v1/paylinks" \
  -H "Authorization: Bearer $NODANA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "nodeId": "node-abcd1234",
    "description": "Support my work",
    "amountSat": null,
    "paymentLimit": null
  }'

Copy the response's url. To add it to your website, replace the example address below with that URL:

<a href="https://pay.nodana.io/link/YOUR_LINK_ID">Leave a tip</a>

In API requests, paymentLimit: null means unlimited. Omitting paymentLimit uses the default of 1. See Create a pay link for all fields and response examples.

For a checkout, calculate the price and create the link on your server. Save a unique checkout reference, expected sats amount, receiving node, and the returned link ID against the order. Use the saved reference in the link description so you can match the node's payment record to this checkout later.

This server-side TypeScript example creates one link for a checkout using Node.js and its built-in fetch. Set NODANA_API_KEY, NODANA_NODE_ID, and SHOP_RETURN_URL in your backend's environment. SHOP_RETURN_URL must be an absolute HTTPS URL, such as https://shop.example/checkout/return.

type PayLink = {
  id: string;
  url: string;
};

async function createCheckoutLink(checkoutReference: string, amountSat: number): Promise<PayLink> {
  const { NODANA_API_KEY, NODANA_NODE_ID, SHOP_RETURN_URL } = process.env;
  if (!NODANA_API_KEY || !NODANA_NODE_ID || !SHOP_RETURN_URL) {
    throw new Error("Set NODANA_API_KEY, NODANA_NODE_ID, and SHOP_RETURN_URL");
  }

  const returnUrl = new URL(SHOP_RETURN_URL);
  returnUrl.searchParams.set("checkout", checkoutReference);

  const response = await fetch("https://api.nodana.io/v1/paylinks", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${NODANA_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      nodeId: NODANA_NODE_ID,
      description: `Checkout ${checkoutReference}`,
      amountSat,
      paymentLimit: 1,
      returnUrl: returnUrl.toString(),
    }),
  });
  if (!response.ok) {
    throw new Error(`Pay link creation failed: ${response.status}`);
  }

  return (await response.json()) as PayLink;
}

Call this from your authenticated checkout handler using a reference generated by your server and the price from your order records. A UUID works well as a checkout reference; keep the complete description within 128 characters. Save link.id before returning link.url to the browser. Reuse that saved URL when the customer resumes the same checkout.

Your website can then send the customer to the hosted page. In this TypeScript example, /api/checkout/pay-link is your own backend endpoint: it checks the customer's cart, calls the function above, and responds with { "url": "..." }.

async function payForCart(cartId: string) {
  const response = await fetch("/api/checkout/pay-link", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ cartId }),
  });
  if (!response.ok) throw new Error("Checkout could not be started");
  const { url } = await response.json();
  window.location.assign(url);
}

If a create request times out, list your pay links and check for the checkout's saved reference before creating another.

Handle the return and verify payment

The customer returns by clicking a button; the payment page does not redirect automatically. The Return URL keeps your existing query parameters and receives:

  • result=paid after Nodana confirms payment, or result=cancelled when the customer clicks Cancel.
  • paymentHash once an invoice has been created. Use this to look up the incoming payment on your node.

For example:

https://shop.example/checkout/return?checkout=YOUR_REFERENCE&result=paid&paymentHash=PAYMENT_HASH

Treat all return parameters as untrusted input. On your server, look up the saved checkout and query its receiving node using the returned payment hash. Use your node's limited Phoenixd password, stored as PHOENIXD_PASSWORD, and its HTTPS connection URL as PHOENIXD_URL:

curl "$PHOENIXD_URL/payments/incoming/$PAYMENT_HASH" \
  --user ":$PHOENIXD_PASSWORD"

Check isPaid, the payment hash, and receivedSat. Match the payment's description to the unique checkout description you saved. Do not fulfil an order based only on result=paid, an amount match, or the customer's return to your website. See Get incoming payment.

Record the verified payment hash against the order and fulfil each order once. Prevent the same hash from being used for another order. Configure signed webhooks to process payments even if the customer closes the page instead of returning; use the payment lookup to retrieve the checkout description when matching a webhook to an order.

Edit a link from its actions menu, turn it off with Enabled, or delete it when you no longer want to issue invoices. Once its confirmed payment count reaches the limit, it stops issuing new invoices. Raise the limit or leave it blank to accept more.

Invoices expire after ten minutes. Checkout offers a new invoice after expiry. Cancel, disabling a link, or deleting a link does not invalidate an invoice already issued: keep checking for payments when reconciling an interrupted order.

Pay Links can create up to two new invoices per minute per IP address and ten per minute per receiving node, shared across its links. Requests that reuse an existing invoice do not count towards these limits. If checkout reaches a limit, wait for the retry period before trying again.

If checkout cannot accept a payment, check that the receiving node is ready, the link is enabled, and the payment limit has not been reached. For an API permission error, check the key's account and its Nodes permissions. If the return button is missing, check that the link has a valid Return URL.

On this page