Embed Widget Flow

From <script> tag to Lightning settlement — every step visualized.

Flow Overview

This diagram traces the complete lifecycle when a merchant adds the SatsRail embed widget to their website. The customer never leaves the merchant's page — checkout happens in an embedded overlay or redirect, with funds settling directly to the merchant's Lightning wallet (non-custodial).

Action
Success
Error
Data
Security
Decision Point
Flow Direction

Sequence Diagram

Step-by-Step Detail

1
Merchant Website Loads

The merchant's HTML page includes a script tag:

<script src="https://satsrail.com/js/pay.js"
  data-product="prod_abc123"
  data-key="pk_live_..."
  data-mode="iframe"></script>

The browser sends a GET request to the SatsRail CDN to fetch the pay.js bundle.

2
Script Initializes

pay.js reads the data-* attributes from the script tag:

  • data-key — validates pk_live_ or pk_test_ prefix format
  • data-product — product slug to fetch price from (or data-amount for direct amount)
  • data-mode — "iframe", "new_tab", or "redirect" (default: uses merchant setting)

If the API key format is invalid, the script stops and renders an error state.

3
Button Rendered

The script injects a styled "Pay with Bitcoin ⚡" button into the DOM at the script tag's position. The button class is .satsrail-pay-btn and can be styled with custom CSS.

4
Customer Clicks Button

pay.js sends a POST request to /api/v1/checkout_sessions with:

Payload:

{ product_id: "prod_abc123", publishable_key: "pk_live_...", success_url, cancel_url }

The CORS endpoint allows wildcard origins, so this works from any merchant domain.

5
SatsRail Creates Checkout Session

The API server:

  1. Validates the publishable key against the merchant's API tokens
  2. Looks up the product — fetches name, description, price (USD)
  3. Converts USD price → sats using the real-time BTC/USD exchange rate
  4. Creates a CheckoutSession record with a 15-minute expiry
  5. Returns checkout_url, token, and expires_at

Rate limiting: API requests are throttled per key. Exceeding limits returns 429 Too Many Requests

6
Checkout Opens

Based on data-mode:

  • iframe: Opens an embedded overlay on the merchant's page (500×700 on desktop, full-screen on mobile). No popup blockers.
  • redirect: Navigates the customer's browser to the SatsRail hosted checkout page.
7
Invoice Generated (Non-Custodial)

SatsRail requests a Lightning invoice from the merchant's connected wallet/node:

  • SatsRail sends the sats amount to the merchant's wallet connection
  • The merchant's node generates a BOLT-11 invoice
  • The invoice is returned to SatsRail and stored in the Invoice record
Non-custodial: SatsRail never holds customer funds. The Lightning invoice is created by the merchant's own node — payment goes directly to the merchant.
8
QR Code Displayed

The checkout page renders:

  • Product name and description
  • Price in USD and equivalent in sats
  • QR code encoding the BOLT-11 invoice
  • "Copy Invoice" button for desktop wallets
  • 15-minute countdown timer for invoice expiry

WebLN-enabled browsers (e.g., Alby extension) are auto-detected — the wallet prompts to pay without scanning.

9
Customer Pays

Three payment paths:

  • Path A — QR Scan: Customer opens their Lightning wallet app (Phoenix, Muun, etc.), scans the QR code, confirms the amount, and sends payment.
  • Path B — Copy & Paste: Customer copies the BOLT-11 string, pastes into a desktop wallet, and confirms.
  • Path C — WebLN: Browser extension wallet (Alby) auto-prompts. Customer clicks "Pay" in the extension popup.
Lightning Settlement (Instant)

Payment routes through Lightning Network channels and arrives at the merchant's wallet instantly. SatsRail detects settlement via:

  • Primary: PaymentMonitorJob polls the merchant's wallet every 5 seconds
  • Secondary: Webhook from the merchant's Lightning node (if configured)

Settlement is final and irreversible — no chargebacks.

Confirmation & Post-Payment

Once confirmed:

  1. Transaction logged: timestamp, amount_sats, USD value at payment time, product_id, payment_hash, preimage
  2. WebSocket broadcast: Checkout page receives real-time "payment confirmed" event
  3. UI updates: Modal/page shows "Payment Confirmed ✓" with transaction details
  4. Redirect: Customer sent to success_url (or shown confirmation if no URL configured)
  5. Webhook fired: Async POST to merchant's webhook URL with full payment payload

Error Paths

Error Trigger User Experience Recovery
Invalid API Key pk_live_ format check fails or key not found in database Script renders error state — no checkout button shown Merchant must fix the data-key attribute
Product Not Found Product slug doesn't match any active product Button shows error message for 3 seconds, then reverts Merchant verifies product slug in dashboard
Invoice Expired 15-minute countdown reaches zero before payment Modal shows "Expired" message Customer clicks "Generate New Invoice" — new invoice at updated BTC rate
Lightning Routing Failure No route found to merchant's node, insufficient channel capacity Error message shown on checkout page Customer retries (may succeed with different routing)
Network Timeout API request from pay.js fails (DNS, connectivity) Button shows "Connection error" briefly pay.js retries with exponential backoff: 1s → 2s → 4s → 8s (max 3 retries)
Session Creation Failed Server error (500) or validation error (422) Button shows error message Customer clicks button again to retry

Security Architecture

Publishable Key Only

The embed widget uses pk_live_ (publishable key) — safe to expose in client-side HTML. The secret key sk_live_ is never used in the widget.

Non-Custodial

SatsRail never holds merchant funds. Lightning invoices are generated by the merchant's own node. Payments flow directly from customer → merchant.

CORS Enabled

The /api/v1/checkout_sessions endpoint allows wildcard CORS origins, so the widget works from any domain without configuration.

HTTPS Only

All communication between pay.js and SatsRail API is over TLS. The CDN serves the script via HTTPS with SRI integrity hashes.

Add Bitcoin payments in 60 seconds

One script tag. No backend. Non-custodial Lightning payments.