LNTPay Docs

Quickstart: Pay per API request

Charge per request with sats. This guide shows how to issue a BOLT11 invoice via the LNTPay SDK and confirm payment using the charge.paid webhook.

This guide walks you through a real pay-per-use flow using Lightning micropayments.

Open Portal

Payment flow overview

End-to-end pay-per-use flow using Lightning invoices and LNTPay webhooks.

User
  ↓
Your API
  ↓  Create charge (Lightning invoice)
LNTPay
  ↓  Lightning invoice (bolt11)
Lightning Wallet
  ↓  Payment settled
LNTPay
  ↓  charge.paid webhook
Your API
  ↓
Unlock API response

1) Prerequisites

  • Node.js >= 18
  • LNTPay account (Portal)
  • API key (create in Portal) with scopes: charges:write, webhooks:read
  • Webhook endpoint + secret (create in Portal)

Environment variables: - LNTPAY_API_KEY - LNTPAY_WEBHOOK_SECRET - LNTPAY_BASE_URL (optional, default: https://api.lntpay.com/v1)

Direct links: - Portal: https://app.lntpay.com/login - Webhooks screen: https://app.lntpay.com/webhooks - API Reference: https://api.lntpay.com/docs

2) Install dependencies

Node.js >= 18 required.

npm init -y
npm install express body-parser @lntpay/sdk

3) Example: pay-per-request API (CommonJS)

The webhook must be registered before express.json() because signature verification requires the raw request body.

require("dotenv").config();

const express = require("express");
const bodyParser = require("body-parser");
const LNTPay = require("@lntpay/sdk").default;

const app = express();
const port = process.env.PORT || 3001;

const client = new LNTPay({
  apiKey: process.env.LNTPAY_API_KEY,
  baseUrl: process.env.LNTPAY_BASE_URL || "https://api.lntpay.com/v1"
});

// In-memory store for the demo (production: use DB/cache)
const paidCharges = new Set();
const pendingCharges = new Set();

// Webhook MUST be registered BEFORE express.json()
// because signature verification requires the raw request body.
app.post(
  "/webhooks/lntpay",
  bodyParser.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.headers["x-lntpay-signature"];

    if (!signature) {
      return res.status(400).send("Missing x-lntpay-signature header");
    }

    try {
      const event = LNTPay.webhooks.constructEvent(
        req.body,
        String(signature),
        process.env.LNTPAY_WEBHOOK_SECRET
      );

      if (event.type === "charge.paid") {
        // Adjust these fields to match LNTPay webhook payload if needed
        const chargeId =
          (event.data && (event.data.charge_id || event.data.id)) ||
          event.charge_id;

        if (chargeId) {
          paidCharges.add(String(chargeId));
          pendingCharges.remove(String(chargeId));
        }
      }

      return res.status(200).send("ok");
    } catch (err) {
      return res.status(400).send("Invalid signature");
    }
  }
);

// JSON parser for the rest of the app
app.use(express.json());

// Example protected resource (pay-per-request)
app.get("/api/data", async (req, res) => {
  const chargeId = req.query.charge_id;

  if (chargeId && paidCharges.has(String(chargeId))) {
    return res.json({ ok: true, data: "Here is your paid content." });
  }

  if (chargeId && pendingCharges.has(String(chargeId))) {
    const pCharge = await client.charges.retrieve(String(chargeId));
    return res.status(200).json({
      ok: true,
      status: pCharge.status,
      reason: "payment_pending",
      charge_id: pCharge.charge_id,
      bolt11: pCharge.bolt11,
      next: `/api/data?charge_id=${pCharge.charge_id}`
    });
  }

  // Not paid yet: create a new charge (BOLT11)
  try {
    const charge = await client.charges.create({
      amount_sats: 10,
      description: "Pay-per-request demo",
      metadata: { feature: "pay_per_request" }
    });
    pendingCharges.add(String(charge.charge_id));

    return res.status(200).json({
      ok: true,
      reason: "payment_required",
      charge_id: charge.charge_id,
      bolt11: charge.bolt11,
      next: `/api/data?charge_id=${charge.charge_id}`
    });
  } catch (err) {
    return res.status(500).json({ ok: false, error: "failed_to_create_charge" });
  }
});

app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}`);
});

4) Webhook notes (important)

  • Use the raw request body.
  • Signature header: x-lntpay-signature.
  • Signature is HMAC SHA-256 (hex) of the raw body using your webhook secret.
  • Respond with HTTP 2xx quickly.

5) User flow (end-to-end)

1) Client calls your /api/data 2) API creates a charge (BOLT11) 3) Client pays the invoice 4) LNTPay sends charge.paid webhook 5) Client retries with charge_id and receives the response

6) Troubleshooting

  • Webhook not arriving: check public URL, secret, and portal logs
  • Invalid signature: ensure raw body and correct secret

Links: - Webhooks docs: https://docs.lntpay.com/webhooks - API Reference: https://api.lntpay.com/docs - Portal webhooks: https://app.lntpay.com/webhooks


Next steps

  • Go to Portal: https://app.lntpay.com/login
  • Explore Webhooks: https://docs.lntpay.com/webhooks
  • See API Reference: https://api.lntpay.com/docs
  • Install SDK: https://www.npmjs.com/package/@lntpay/sdk