OpenReceive for NestJS
Accept Bitcoin & Stablecoin Payments With NestJS
Mount OpenReceive's Express adapter in main.ts with three hooks. Your services set the price and decide who may pay, your TypeORM DataSource keeps the payment attempts, and settlement runs inside your own Nest server.
Tested live · Oct 13, 2026 · 0.4.24
Copy agent directions — Paste into Claude Code, Codex or Cursor to add OpenReceive to your NestJS app.
Why OpenReceive
- No OpenReceive account. There is no sign-up, no API key and no dashboard. Install the package, add a receive-only wallet code, and your server issues invoices itself.
- Open source, inside your server. MIT-licensed routes mount in the app you already run. OpenReceive never sees an order, a price or a customer, and never holds funds.
- Receive-only wallet permissions. The one credential is a receive-only NWC code. It can create invoices and read their status. It cannot spend, so a compromised server cannot drain the wallet.
- Stablecoins, optional. Connect a swap provider and customers can pay in USDT, USDC, ETH or SOL. Every payment still settles to your wallet in Bitcoin.
Try the checkout your customers will see
The live demo needs JavaScript: open the OpenReceive home page.
Set up
Requires Node ≥ 22, NestJS ≥ 10 on the Express platform.
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 NestJS server process; see
Environment variables.
4. Install
npm install @openreceive/express @openreceive/http @openreceive/react
5. Wire OpenReceive — quoted from the NestJS quickstart.
import "dotenv/config"; // drop this line if ConfigModule.forRoot() loads .env
import { NestFactory } from "@nestjs/core";
import type { NestExpressApplication } from "@nestjs/platform-express";
import { openReceiveExpress } from "@openreceive/express";
import { typeOrmDb } from "@openreceive/http";
import type { Request } from "express";
import { DataSource } from "typeorm";
import { AppModule } from "./app.module";
import { AuthService } from "./auth/auth.service"; // your existing services
import { OrdersService } from "./orders/orders.service";
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// The app's own app.use(session(...)), cookie-parser or passport.session()
// stay above the mount, so the session is on the request when authorize runs.
const orders = app.get(OrdersService);
const auth = app.get(AuthService);
// Nest adds its JSON body parser only at listen, after this mount. Without
// this line every checkout request fails with a 500.
app.useBodyParser("json");
app.use(
openReceiveExpress({
wallet: { nwc: process.env.NWC_URI! }, // receive-only NWC code; your app refuses to start otherwise
storage: {
// The app's TypeORM DataSource. Prisma: prismaDb(app.get(PrismaService), "postgres").
db: typeOrmDb(app.get(DataSource), "postgres"),
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. Use
// `query` here, not a repository: it runs in this same transaction.
// `$1` on postgres, `?` on sqlite.
await query(
"UPDATE orders SET status = 'paid', paid_at = $1 WHERE id = $2 AND status = 'awaiting_payment'",
[paidAt, reference],
);
},
},
// The price for a reference (your order id, as a string) from your own
// data. `value` is a decimal STRING from the order row, never a float and
// never a request param. `description` is what the payer is buying.
amountFor: async (reference) => {
const order = await orders.findOne(reference);
return order
? {
currency: "USD",
value: order.total.toString(),
description: `${order.lines.length} items`,
}
: null;
},
// Nest's guards do not run here: make the check your guard makes.
// `native` is the Express request your session, cookie-parser or
// Passport middleware already filled in. `request` is a Web Request
// without any of that, so a helper given it finds no session and every
// payer gets a 403. `resource.reference` is a claim from the payer's
// browser, not proof.
authorize: async ({ action, native, resource }) =>
orders.viewerMay(await auth.currentUser(native as Request), resource.reference, action),
// Recommended for public web shops: caps invoice creation at 60 per
// client IP per hour. Leave it off for point-of-sale deployments, where
// many payers share the terminal's IP.
rateLimiting: true,
}),
);
// Behind a reverse proxy, rate limiting needs the real client IP. Delete the
// line only if the app faces the network directly (see the rate-limiting guide).
app.set("trust proxy", 1);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
6. Render the checkout
import { Checkout } from "@openreceive/react";
import "@openreceive/react/styles.css";
<Checkout reference={order.id} prefix="/openreceive" />;
Full NestJS quickstart · Example app
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?
- Node 22 or later, NestJS 10 or later on the Express platform, NWC_URI in the server environment, and the OpenReceive payment-table migration applied through your normal migration tool. NestJS quickstart
Add OpenReceive to your NestJS app
Copy agent directions · Full quickstart · Example repository · API reference · Guides