# Embed Widget Payment Flow

> Complete sequence diagram showing how the SatsRail embed widget (pay.js) loads, renders, creates checkout sessions, and processes Lightning payments — from script tag to settlement.

Source: https://www.satsrail.com/developers/guides/flows/embed-widget/

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).

## 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.

[Embed Button Docs →](https://www.satsrail.com/developers/guides/embed/) [Checkout API →](https://www.satsrail.com/developers/)
