Node framework quickstart

Fastify payments without the scary bits

Pick your framework and only the relevant route code stays in view. Each framework has its own crawlable URL, so you can link straight to the docs your team needs.

Raw Node

Fastify

Use the raw Node handler and let OpenReceive write the hijacked response.

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.

Server environment
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.

Install packages
npm install @openreceive/node @openreceive/express @openreceive/browser pg
Setup check
npx openreceive doctor

Fastify server route

Live checkout always needs a server component. Browser code never receives OPENRECEIVE_NWC.

Create server/openreceive.ts
import {
  createOpenReceiveFetchHandler,
  createOpenReceiveFetchRuntime,
  createOpenReceiveNodeHandler
} from "@openreceive/express";
import {
  createAlbyNwcReceiveClient,
  formatOpenReceiveMissingNwcMessage,
  resolveOpenReceiveStore
} from "@openreceive/node";

let runtime;
const store = await resolveOpenReceiveStore();

function getRuntime() {
  if (runtime) return runtime;

  const nwc = process.env.OPENRECEIVE_NWC;
  if (!nwc) {
    const message = formatOpenReceiveMissingNwcMessage();
    console.error(message);
    throw new Error(message);
  }

  runtime = createOpenReceiveFetchRuntime({
    client: createAlbyNwcReceiveClient({
      connectionString: nwc
    }),
    store,
    merchantScope: () => "app:default",
    auth: {
      create: (req) => isAllowedToCreateInvoice(req),
      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
      });
    }
  });

  return runtime;
}

export const openreceive = createOpenReceiveFetchHandler({
  runtime: getRuntime
});

export const openreceiveNode = createOpenReceiveNodeHandler({
  runtime: getRuntime
});
Mount the raw Node handler in Fastify
import { openreceiveNode } from "./server/openreceive";

fastify.all("/openreceive/v1/*", async (request, reply) => {
  reply.hijack();
  await openreceiveNode(request.raw, reply.raw);
});

Browser checkout

Your UI creates an invoice by posting to your OpenReceive server route.

Create an invoice
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();
React checkout UI
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.

Processes
web                 npm start
optional scheduler  npx openreceive poll --once
One recovery pass
npx openreceive poll --once
Storage note: Production today should use package-owned Postgres storage. SQLite is for single-machine self-hosting, local development, demos, and small apps.