Express
Mount OpenReceive as normal Express routes under /openreceive/v1.
- Server package
@openreceive/express- API mount
/openreceive/v1- Secret location
- Server only
Common setup
First, get a receive-only NWC code so your server can create invoices and check payment status. You can switch NWC providers later without changing the browser checkout.
OPENRECEIVE_NWC=nostr+walletconnect://...
OPENRECEIVE_STORE=local-sqlite
OPENRECEIVE_NAMESPACE=default
Use postgres://... for production storage, or
sqlite:///abs/path/openreceive.sqlite3 for an explicit single-machine file.
The store initializes itself on boot.
npm install @openreceive/node @openreceive/express @openreceive/browser express pg
npx openreceive doctor
Express server route
Live checkout always needs a server component. Browser code never receives
OPENRECEIVE_NWC.
import {
createAlbyNwcReceiveClient,
formatOpenReceiveMissingNwcMessage,
resolveOpenReceiveStore
} from "@openreceive/node";
const nwc = process.env.OPENRECEIVE_NWC;
if (!nwc) {
const message = formatOpenReceiveMissingNwcMessage();
console.error(message);
throw new Error(message);
}
const store = await resolveOpenReceiveStore();
export const openreceive = {
client: createAlbyNwcReceiveClient({
connectionString: nwc
}),
store,
merchantScope: (req) => `user:${req.user.id}`,
auth: {
create: (req) => Boolean(req.user),
read: (req, invoice) => ownsInvoice(req, invoice),
lookup: (req, invoice) => ownsInvoice(req, invoice),
refresh: (req, invoice) => ownsInvoice(req, invoice),
poll: (req) => isInternalScheduler(req)
},
csrf: {
verify: (req) => verifyCsrf(req)
},
settlementAction: async ({ invoice, metadata }) => {
await markOrderPaid({
invoiceId: invoice.invoice_id,
orderId: metadata.order_id
});
}
};
import express from "express";
import { mountOpenReceiveExpressRoutes } from "@openreceive/express";
import { openreceive } from "./server/openreceive";
const app = express();
app.use(express.json());
mountOpenReceiveExpressRoutes(app, openreceive);
app.listen(3000);
Browser checkout
Your UI creates an invoice by posting to your OpenReceive server route.
const response = await fetch("/openreceive/v1/invoices", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": orderId
},
body: JSON.stringify({
fiat: {
currency: "USD",
value: "10.00"
},
metadata: {
order_id: orderId
}
})
});
const invoice = await response.json();
import { OpenReceiveCheckout } from "@openreceive/react";
import "@openreceive/react/styles.css";
export function Checkout({ invoice }) {
return (
<OpenReceiveCheckout
{...invoice}
lookupUrl="/openreceive/v1/invoices/lookup"
/>
);
}
No-framework apps can use @openreceive/elements or lower-level @openreceive/browser helpers.
Recovery
OpenReceive does not need a daemon or wallet notification listener. Browser lookups and bounded route-triggered sweeps use backend invoice lookup, and an optional scheduler can run one extra recovery pass.
web npm start
optional scheduler npx openreceive poll --once
npx openreceive poll --once