Widget Overview

Three widget variants cover the common checkout patterns. Pick the one that matches your use case.

Widgets are the fastest way to accept payments on your own site. Drop in a script tag and a target element, and a styled Pay button appears that opens a payment modal when clicked. No payment code on your end.

Quick start

Add the SDK to your page once, then drop a data-sbtcpay attribute anywhere you want a Pay button:

Invoice payment
<!-- 1. Load the SDK once per page (use async) -->
<script src="https://sbtc-pay.com/sbtcpay.js" async></script>

<!-- 2. Drop a Pay button anywhere -->
<div data-sbtcpay="invoice" data-sbtcpay-invoice="123"></div>

The SDK scans for [data-sbtcpay] elements on load (and again on DOM changes for SPAs), renders a styled button into each one, and opens a modal payment dialog on click. The dialog runs the existing sBTC Pay widget inside a sandboxed iframe — your page never touches wallet data.

Two integration modes

The script tag is the recommended path — it gives you a polished Pay button + modal flow. If you need the widget inline in the page (no click trigger), the bare iframe still works — see URL Parameters.

The three widget variants

1. Direct payment

Fixed-amount payment button — donation pages, one-off purchases, simple digital-good checkouts. No invoice is created; the payment goes straight into your merchant wallet.

Direct payment button
<div
  data-sbtcpay="direct"
  data-sbtcpay-merchant="SP1ABC...XYZ"
  data-sbtcpay-amount="100000"
  data-sbtcpay-token="sbtc"
  data-sbtcpay-memo="Coffee tip"
></div>

2. Invoice payment

Per-order checkout. Create an invoice via your dashboard (or the API), then embed a button with that invoice ID. The widget pulls amount, memo, and status directly from on-chain.

Invoice payment button
<div data-sbtcpay="invoice" data-sbtcpay-invoice="123"></div>

3. Subscription

Recurring billing. Puts a Subscribe button on your pricing page — when a customer clicks, the subscription is created on-chain and appears in their Customer Portal.

Subscription button
<div
  data-sbtcpay="subscribe"
  data-sbtcpay-merchant="SP1ABC...XYZ"
  data-sbtcpay-plan="Pro"
  data-sbtcpay-amount="50000"
  data-sbtcpay-interval="monthly"
  data-sbtcpay-token="sbtc"
></div>

Listening for events (declarative)

When you use the data-sbtcpay attribute to auto-mount a Pay button, you can still observe payment events by listening for CustomEvents on window:

Listen for events from auto-mounted buttons
<div data-sbtcpay="invoice" data-sbtcpay-invoice="123"></div>

<script src="https://sbtc-pay.com/sbtcpay.js" async></script>
<script>
  window.addEventListener('sbtcpay:success', (e) => {
    // e.detail = { mode, txId, invoiceId? }
    console.log('Paid', e.detail.txId);
    // e.g. fire analytics, redirect, refresh inventory
  });

  window.addEventListener('sbtcpay:error', (e) => {
    console.error(e.detail.message);
  });

  window.addEventListener('sbtcpay:close', (e) => {
    console.log('User closed the modal (mode:', e.detail.mode, ')');
  });
</script>

Programmatic API

Once sbtcpay.js loads, a global SBTCPay object is available. Use it to open the modal from your own JS (e.g. after a custom action), and to receive success / error / close callbacks:

Open modal from JS, listen for events
<button id="checkout">Checkout</button>

<script src="https://sbtc-pay.com/sbtcpay.js" async></script>
<script>
  document.getElementById('checkout').addEventListener('click', () => {
    SBTCPay.open({
      mode: 'invoice',
      invoiceId: 123,
      onSuccess: ({ txId, invoiceId }) => {
        console.log('Paid', txId, invoiceId);
        // e.g. redirect to thank-you page
        window.location.href = '/thank-you?tx=' + txId;
      },
      onError: ({ message }) => console.error(message),
      onClose: () => console.log('User closed the modal'),
    });
  });
</script>

Button label override

Override the default Pay button text with data-sbtcpay-label:

Custom label
<div
  data-sbtcpay="direct"
  data-sbtcpay-merchant="SP1ABC...XYZ"
  data-sbtcpay-amount="500000"
  data-sbtcpay-label="Buy me a coffee ☕"
></div>

Data attribute reference

AttributeApplies toDescription
data-sbtcpayallMode: direct, invoice, or subscribe
data-sbtcpay-merchantdirect, subscribeMerchant Stacks principal
data-sbtcpay-invoiceinvoiceNumeric invoice ID
data-sbtcpay-amountdirect, subscribeAmount in base units (sats for sBTC, microSTX for STX)
data-sbtcpay-tokendirect, subscribesbtc (default) or stx
data-sbtcpay-memodirectOptional memo string (max 200 chars)
data-sbtcpay-plansubscribePlan name shown to the customer
data-sbtcpay-intervalsubscribedaily, weekly, biweekly, monthly, quarterly, yearly
data-sbtcpay-labelallOverride the button text
data-sbtcpay-themealldark (default) or light

Why a script tag (not just an iframe)?

Stripe-style integration: the script tag gives you a real button + modal flow that feels native to your site, instead of an embedded iframe block taking up page space. Under the hood, the payment UI still loads from sBTC Pay's domain inside a sandboxed iframe — so:

  • Customer wallet data stays in sBTC Pay's domain. Your page's JS can't read or leak payment details.
  • Your page's CSS can't break the widget. A buggy rule on your site won't corrupt the wallet prompt.
  • SDK is small. The loader is a few KB of vanilla JS — React/wallet code only loads inside the iframe when the modal opens.

Self-hosting the widget

sBTC Pay is open source. To host the SDK yourself, clone the repo and serve frontend/public/sbtcpay.js from your domain. The script auto-detects the origin it was loaded from, so a script served from testnet.sbtc-pay.com opens the testnet widget; a script served from sbtc-pay.com opens mainnet.

Testing your embed

Use the testnet SDK during development. Switch to the mainnet URL only when you're ready to accept real Bitcoin:

  • Testnet: https://testnet.sbtc-pay.com/sbtcpay.js
  • Mainnet: https://sbtc-pay.com/sbtcpay.js

iframe (inline) — alternative

For when you need the payment UI visible in the page without a click trigger (e.g. a dedicated checkout page that is the payment surface), the bare iframe still works. The URL Parameters reference documents the iframe URL format. The Widget Generator in your dashboard outputs both formats.