BTCPay Server · Drop-in Checkout

SOV
SATS

Checkout Without Compromise

A production-grade Bitcoin checkout UI for BTCPay Server. Mobile-first, real-time confirmation, framework agnostic.

Built for merchants who don't have payment processor options — and don't want one.

INSTALL npm install sovsats
SovSats Bitcoin checkout on a phone
// Demo

Real checkout,
in motion.

Screen recording of the live flow (place files in docs/assets/ — see PLACE_FILES_HERE.txt).

4
Env vars to ship
1
Component to drop in
0
Payment processor deps
Times Stripe can ban you

BTCPay is right.
The UI is not.

Every merchant running BTCPay is staring at the same generic checkout. SovSats is the application layer that was missing.

Redirect on Detection

Customer sees confirmation the moment payment hits the mempool. No waiting. No "keep this page open" anxiety. The UX your customers deserve.

🔒
Fulfill on Settlement

Your backend waits for real on-chain confirmation before fulfilling. Protects you from fraud. Both phases handled correctly, out of the box.

📦
Framework Agnostic Core

Pure TypeScript core with no framework deps. Adapters for Next.js App Router and Express ship with the package. Your setup, your rules.

🎯
One Component

Drop in BtcNexusCheckout, wire your BTCPay URL and API key, done. The whole checkout — pay state, polling, confirmation — is one file.

Four env vars.
One component.
Shipped.

No merchant dashboard to sign up for. No approval process. You own your BTCPay instance, you own your checkout.

01
Set env vars

BTCPAY_SERVER_URL, BTCPAY_STORE_ID, BTCPAY_API_KEY, BTCPAY_WEBHOOK_SECRET

02
Export route handlers

Two files, two lines each. Create and poll endpoints are ready.

03
Drop in the component

Pass invoice data as props. Handle onSettled to fulfill your order. Done.

app/api/payments/btcpay/route.ts TS
export { POST } from "sovsats/adapters/next";
app/api/webhooks/btcpay/route.ts TS
import { makeWebhookHandler } from "sovsats/adapters/next";

export const POST = makeWebhookHandler({
  onSettled: async (invoiceId) => {
    // fulfill order here — fully confirmed
    await fulfillOrder(invoiceId);
  },
});
checkout.tsx TSX
import { BtcNexusCheckout } from "sovsats/react";
import { buildBitcoinUri } from "sovsats";

<BtcNexusCheckout
  invoiceId={invoice.invoiceId}
  pollEndpoint="/api/payments/btcpay"
  storeName="Your Store"
  usdTotal="$49.99"
  btcAddress={btc.address}
  btcAmount={btc.due}
  bitcoinUri={buildBitcoinUri(btc.address, btc.due, invoice.invoiceId)}
  orderId="ORD-12345"
  callbacks={{
    onSettled: (id) => router.push(`/success`),
  }}
/>

Both phases.
Handled right.

The two-phase confirmation split is the part most implementations get wrong. SovSats handles it correctly by default.

BTCPay Status SovSats Behavior Safe For
NEW Polling · waiting for mempool
PROCESSING Fires onProcessing · UX redirect UX redirect only
SETTLED Fires onSettled · stops polling Order fulfillment ✓
EXPIRED Shows expired state · stops polling

If Stripe banned you,
you're home.

Built for the merchants traditional processors won't touch. Your customers still need to pay. Now they can — with a checkout that doesn't look like an afterthought.

Kratom Vendors
CBD & Hemp
Nootropics
Supplements
Adult Products
Firearms Accessories
Research Compounds
High-Risk E-Commerce
Any BTCPay Merchant

Ready to
ship?

Four env vars. One component. No approval process.
You own the checkout. You own the stack.

Get Started on GitHub npm install sovsats