OpenReceive for Next.js

Accept Bitcoin & Stablecoin Payments With Next.js

Export the OpenReceive handlers from one App Router route and drop the checkout into a client component. No separate API server, and settlement is serverless-safe.

Package

Tested live · Oct 14, 2026 · 0.4.26

Copy agent directions — Paste into Claude Code, Codex or Cursor to add OpenReceive to your Next.js app.

Why OpenReceive

Try the checkout your customers will see

The live demo needs JavaScript: open the OpenReceive home page.

Six steps, real code

Requires Node ≥ 22, Next.js ≥ 15 (App Router).

1. Get a receive-only NWC code — get one here. It can issue invoices and watch for payment, and it can never spend from your wallet.

2. Get an LSC code for swaps (optional) — from a swap provider, only if customers should be able to pay in USDT, USDC, ETH or SOL.

3. Set the environment variables — NWC_URI (required) and LSC_URI_PRIMARY / LSC_URI_BACKUP (optional) in the environment of your Next.js server process; see Environment variables.

4. Install

npm install @openreceive/next @openreceive/react

5. Wire OpenReceive — quoted from the Next.js quickstart.

// app/openreceive/[...openreceive]/route.ts
import { openReceiveNextHandlers } from "@openreceive/next";
import { db, orders, sessions } from "@/lib/app"; // your existing database handle and models

// The wallet relay and your database driver need Node, never the Edge runtime,
// and a payment route must never be cached or statically rendered.
export const runtime = "nodejs";
export const dynamic = "force-dynamic";

export const { GET, POST } = openReceiveNextHandlers({
  wallet: { nwc: process.env.NWC_URI! }, // receive-only NWC code; your app refuses to start otherwise
  storage: {
    db, // pg Pool/Client, mysql2 Pool, node:sqlite, better-sqlite3, or a custom adapter
    onPaid: async ({ reference, paidAt, query }) => {
      // Settlement transaction; runs only for the first settled attempt for a
      // reference. The WHERE clause is the lock: a second fulfillment path of
      // yours (admin action, replayed job) updates zero rows and does nothing.
      // Use `query` here, not your ORM's other connection. `?` on sqlite and
      // mysql, `$1` on postgres. MySQL has no RETURNING: after the UPDATE,
      // `SELECT ROW_COUNT() AS claimed` tells you the same thing.
      const claimed = await query(
        "UPDATE orders SET state = 'paid', paid_at = ? WHERE id = ? AND state = 'awaiting_payment' RETURNING id",
        [paidAt, reference],
      );
      if (claimed.length === 0) return;
    },
  },
  // The price for a reference — here, your order id — from your own data;
  // OpenReceive converts it into the Lightning invoice. Return null when
  // there is nothing to pay for. `value` is a decimal STRING from the order
  // row, never a float and never a request param. `description` is what the
  // payer is buying, in your own words.
  amountFor: async (reference) => {
    const order = await orders.find(reference);
    return order
      ? {
          currency: "USD",
          value: order.total.toString(),
          description: `${order.lines.length} items`,
        }
      : null;
  },
  // Your own access check: may this caller do this action to this reference?
  // `resource.reference` is your own order id, sent back by the payer's
  // browser — a claim, not proof — already validated as a non-empty string.
  // `request` is the Web Request; read cookies or headers from it the way you
  // would in any route handler (`native` is the same NextRequest).
  authorize: async ({ action, request, resource }) =>
    orders.viewerMay(
      await sessions.currentUser(request),
      resource.reference,
      action,
    ),
  // Recommended for public web shops: caps invoice creation at 60 per client IP
  // per hour. A web Request has no socket IP, so on Next this ALSO needs an
  // IP source: `trustProxyIpHeader: true` reads the first hop of
  // x-forwarded-for, which is safe only when YOUR reverse proxy or hosting
  // platform sets it (Vercel, Cloudflare and most load balancers do). Without
  // an IP source the adapter refuses to construct rather than run an
  // inactive limiter. Leave both off for point-of-sale deployments, where
  // many payers share the terminal's IP.
  rateLimiting: true,
  trustProxyIpHeader: true,
});

6. Render the checkout

// app/checkout/[reference]/order-checkout.tsx
"use client";

import { Checkout } from "@openreceive/react";
import "@openreceive/react/styles.css";

export function OrderCheckout({ reference }: { reference: string }) {
  return <Checkout reference={reference} prefix="/openreceive" />;
}

Full Next.js quickstart · Example app

How it runs

Tested on
Node 22
Databases
PostgreSQL, directly or through a transaction pooler, MySQL 8, MariaDB 11 and SQLite
Secrets
NWC_URI, and LSC_URI_PRIMARY for swaps, in the server's environment or .env file, never in browser code
Refunds
Refund a settled Lightning payment from your own wallet: the wallet code is receive-only. A swap deposit that never converted goes back to the payer through the checkout's refund form.
Mobile apps
No app SDK. A mobile app opens your checkout page in a browser tab, which the app stores allow for physical goods and real-world services; digital goods need a link-out program.

Practical questions

Which currencies can customers pay with?
Bitcoin over Lightning, always. With a swap provider configured, also USDT on Tron, Solana or Ethereum, USDC on Solana or Ethereum, ETH and SOL, each converted into the same Lightning payment. Automated swaps guide
Where does the Bitcoin go?
Into the wallet behind your NWC code. OpenReceive never holds funds: the invoice is minted in your wallet and the sats land there. Your server confirms settlement and runs your onPaid hook once per order. Payment storage guide
What wallet do I need?
Any wallet that issues receive-only NWC (Nostr Wallet Connect) codes, hosted or self-run. The code must be able to create invoices and read their status, and must not be able to spend. Get a receive-only NWC code
What does it cost?
OpenReceive is open source and free. Fees charged by your wallet provider, by a swap provider or by Lightning routing belong to those services and are outside OpenReceive's scope.
What do I need to deploy?
A server process with NWC_URI in its environment and a database for the payment tables. No background worker is required: settlement runs on the mounted routes, with an optional notification worker for faster confirmation. Deploying guide

Add OpenReceive to your Next.js app

Copy agent directions · Full quickstart · Example app · API reference · Guides

Related

Vercel · v0