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:
<!-- 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 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.
<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.
<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.
<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:
<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:
<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:
<div
data-sbtcpay="direct"
data-sbtcpay-merchant="SP1ABC...XYZ"
data-sbtcpay-amount="500000"
data-sbtcpay-label="Buy me a coffee ☕"
></div>Data attribute reference
| Attribute | Applies to | Description |
|---|---|---|
data-sbtcpay | all | Mode: direct, invoice, or subscribe |
data-sbtcpay-merchant | direct, subscribe | Merchant Stacks principal |
data-sbtcpay-invoice | invoice | Numeric invoice ID |
data-sbtcpay-amount | direct, subscribe | Amount in base units (sats for sBTC, microSTX for STX) |
data-sbtcpay-token | direct, subscribe | sbtc (default) or stx |
data-sbtcpay-memo | direct | Optional memo string (max 200 chars) |
data-sbtcpay-plan | subscribe | Plan name shown to the customer |
data-sbtcpay-interval | subscribe | daily, weekly, biweekly, monthly, quarterly, yearly |
data-sbtcpay-label | all | Override the button text |
data-sbtcpay-theme | all | dark (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.