> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shoppex.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Embed SDK reference

> Complete reference for the Shoppex Checkout Embed SDK, plus CSP, origin security, and the production checklist

This page documents the current behavior of the Checkout Embed SDK (`window.Shoppex`): the full API, data attributes, events, security model, and troubleshooting.

## Script and integration modes

```html theme={"system"}
<script src="https://checkout.shoppex.io/embed/embed.iife.js" defer></script>
```

Global object:

```javascript theme={"system"}
window.Shoppex
```

<Tabs>
  <Tab title="Declarative (data attributes)">
    The SDK auto-binds clickable elements matching:

    * `[data-shoppex-product-id]`
    * `[data-shoppex-group-id]`
    * `[data-shoppex-checkout]`

    Simple button:

    ```html theme={"system"}
    <button
      data-shoppex-product-id="PRODUCT_ID"
      data-shoppex-variant-id="VARIANT_ID"
      data-shoppex-quantity="1"
      data-shoppex-theme="auto"
    >
      Buy now
    </button>
    ```

    Multiple items with JSON:

    ```html theme={"system"}
    <button
      data-shoppex-checkout="1"
      data-shoppex-items='[
        {"productId":"PRODUCT_ID_1","variantId":"VARIANT_1","quantity":1},
        {"productId":"PRODUCT_ID_2","quantity":2}
      ]'
    >
      Checkout
    </button>
    ```

    Group button:

    ```html theme={"system"}
    <button
      data-shoppex-group-id="GROUP_ID"
      data-shoppex-referral-code="CREATOR123"
      data-shoppex-return-url="https://your-site.com/thank-you"
    >
      Open group
    </button>
    ```
  </Tab>

  <Tab title="Programmatic (JS API)">
    ```javascript theme={"system"}
    Shoppex.open({
      shopId: 'YOUR_SHOP',
      items: [
        { productId: 'PRODUCT_ID', variantId: 'VARIANT_ID', quantity: 1 }
      ],
      theme: 'auto',
      email: 'customer@example.com',
      couponCode: 'SAVE20',
      affiliateCode: 'CREATOR123',
      returnUrl: 'https://your-site.com/thank-you',
      metadata: { campaign: 'launch' }
    });
    ```
  </Tab>
</Tabs>

## Data attributes

| Attribute                     | Required | Description                              |
| ----------------------------- | -------- | ---------------------------------------- |
| `data-shoppex-product-id`     | Yes\*    | Product identifier                       |
| `data-shoppex-group-id`       | Yes\*\*  | Group identifier                         |
| `data-shoppex-variant-id`     | No       | Variant identifier                       |
| `data-shoppex-quantity`       | No       | Quantity (`1..999`, default `1`)         |
| `data-shoppex-theme`          | No       | `light`, `dark`, `auto` (default `auto`) |
| `data-shoppex-return-url`     | No       | Return URL after checkout                |
| `data-shoppex-email`          | No       | Prefill customer email                   |
| `data-shoppex-coupon-code`    | No       | Prefill coupon code                      |
| `data-shoppex-affiliate-code` | No       | Affiliate code                           |
| `data-shoppex-referral-code`  | No       | Referral code alias                      |
| `data-shoppex-metadata`       | No       | JSON object with string values           |
| `data-shoppex-items`          | No       | JSON array of checkout items             |
| `data-shoppex-shop-id`        | No       | Optional field in current API payload    |
| `data-shoppex-checkout`       | No       | Marker selector for custom triggers      |

\* Required unless `data-shoppex-items` contains at least one valid item.
\*\* Required when you want to open a group instead of a product checkout.

`data-shoppex-metadata` example:

```html theme={"system"}
<button
  data-shoppex-product-id="PRODUCT_ID"
  data-shoppex-metadata='{"campaign":"summer","source":"landing"}'
>
  Buy now
</button>
```

## JavaScript API

### `Shoppex.init(config?)`

Initializes bindings and keyboard handlers.

```javascript theme={"system"}
Shoppex.init({
  nonce: 'YOUR_CSP_NONCE'
});
```

Notes:

* This call is usually optional because the script initializes automatically.
* It is safe to call multiple times. The first call wins.

### `Shoppex.open(options)`

Opens modal checkout.

```typescript theme={"system"}
type CheckoutOptions = {
  shopId?: string;
  groupId?: string;
  items: Array<{
    productId: string;
    variantId?: string;
    quantity?: number;
  }>;
  theme?: 'light' | 'dark' | 'auto';
  returnUrl?: string;
  email?: string;
  couponCode?: string;
  affiliateCode?: string;
  metadata?: Record<string, string>;
};
```

Important behavior:

* `items` must contain at least one valid `productId`, unless `groupId` is provided.
* `groupId` opens a group picker first, then forwards the buyer into the normal product checkout page.
* If more than one item is passed, the current modal flow uses the first valid item.
* Calling `open()` while a modal is open closes the old modal first.

### `Shoppex.close()`

Closes the modal programmatically.

```javascript theme={"system"}
Shoppex.close();
```

## Events

Listen on `document`.

```javascript theme={"system"}
document.addEventListener('shoppex:success', (event) => {
  console.log(event.detail.invoiceId);
});

document.addEventListener('shoppex:error', (event) => {
  console.error(event.detail.error);
});
```

Event payloads:

| Event             | Detail                   |
| ----------------- | ------------------------ |
| `shoppex:success` | `{ invoiceId?: string }` |
| `shoppex:error`   | `{ error?: string }`     |

Internal iframe events used by the SDK:

* `shoppex:ready`
* `shoppex:resize`
* `shoppex:close`

**Practical example**

```html theme={"system"}
<button id="buy">Buy now</button>
<script src="https://checkout.shoppex.io/embed/embed.iife.js" defer></script>
<script>
  document.getElementById('buy').addEventListener('click', () => {
    Shoppex.open({
      items: [{ productId: 'PRODUCT_ID', quantity: 1 }],
      theme: 'auto'
    });
  });

  document.addEventListener('shoppex:success', (event) => {
    console.log('Invoice paid:', event.detail.invoiceId);
  });
</script>
```

## URL mapping and runtime behavior

The SDK resolves checkout iframe URLs to:

```text theme={"system"}
https://checkout.shoppex.io/e/:productId
https://checkout.shoppex.io/g/:groupId
```

Supported query params sent by the SDK:

* `quantity`
* `variantId`
* `theme` (only when not `auto`)
* `flow`
* `shopId`
* `returnUrl`
* `email`
* `couponCode`
* `affiliateCode`
* `metadata[key]=value`

Runtime behavior:

* The modal uses Shadow DOM (`mode: closed`) for style isolation.
* `Escape`, a backdrop click, and the close button all close the modal.
* Dynamically inserted buttons are auto-bound through a MutationObserver.
* Message handling accepts only trusted checkout origins.

## Security

<Note>
  The SDK protects modal message handling by validating the iframe origin before it processes events. It accepts messages only from trusted origins, so a forged `postMessage` event from a random origin is ignored.
</Note>

Trusted origins:

* `https://checkout.shoppex.io`
* the local development origin for the checkout app

### CSP setup

<Warning>
  If you enforce a Content Security Policy and omit the required `frame-src` and `script-src` rules, the checkout iframe is blocked entirely. Customers see a blank modal or a browser console error instead of the payment form. Always test your CSP in production, not only locally.
</Warning>

If you run a strict CSP, allow the checkout iframe and script, and pass a nonce to `Shoppex.init`.

Example policy:

```http theme={"system"}
Content-Security-Policy:
  frame-src https://checkout.shoppex.io;
  script-src 'self' 'nonce-YOUR_NONCE' https://checkout.shoppex.io;
  style-src 'self' 'nonce-YOUR_NONCE';
```

SDK init with nonce:

```javascript theme={"system"}
Shoppex.init({ nonce: 'YOUR_NONCE' });
```

Why this matters:

* The SDK injects styles into its shadow root.
* The `nonce` lets those injected styles run under a strict CSP.

### Return URL safety

<Warning>
  Never pass unvalidated user input as `returnUrl`. An attacker can exploit this as an open redirect, sending customers to a phishing page after checkout completes. Always use a hardcoded or server-validated URL.
</Warning>

Use a trusted application URL for `returnUrl`.

```javascript theme={"system"}
Shoppex.open({
  items: [{ productId: 'PRODUCT_ID' }],
  returnUrl: 'https://app.example.com/checkout/success'
});
```

Avoid sending unvalidated user input directly as `returnUrl`.

## Production checklist

<Steps>
  <Step title="Use HTTPS everywhere">
    The embed modal runs inside a cross-origin iframe. Most browsers block mixed content, so your host page must be served over HTTPS.
  </Step>

  <Step title="Load the embed script from the correct origin">
    Always load from `https://checkout.shoppex.io/embed/embed.iife.js`. Never self-host or proxy the script, because it must match the iframe origin.
  </Step>

  <Step title="Bind event handlers">
    Listen for `shoppex:success` and `shoppex:error` events so your app knows when a payment completed or failed. Without these, your UI has no feedback loop.
  </Step>

  <Step title="Track conversions">
    Fire your analytics conversion event inside the `shoppex:success` handler. This is the only reliable moment to attribute a sale to your funnel.
  </Step>

  <Step title="Validate product and variant IDs">
    Double-check that `data-shoppex-product-id` and `data-shoppex-variant-id` values match real products. Invalid IDs silently fail to open the modal.
  </Step>

  <Step title="Test dark mode and mobile viewports">
    The modal adapts to `prefers-color-scheme` and small screens. Test both to catch layout issues before your customers do.
  </Step>

  <Step title="Validate CSP in production">
    Your local dev server likely has no CSP. Test your Content Security Policy on the real production domain, because CSP violations only surface there.
  </Step>
</Steps>

## Troubleshooting

### Modal does not open

Checklist:

* The script is loaded.
* The element has a valid `data-shoppex-product-id`, `data-shoppex-group-id`, or `data-shoppex-items`.
* `items` contains at least one non-empty `productId`, unless you pass a valid `groupId`.
* No JavaScript error runs before the click handler.

### Wrong product opens

Cause: multiple `items` currently resolve to the first valid item in the modal flow.

Fix: pass one item per checkout open until multi-item modal support lands.

### Event listeners do not fire

Checklist:

* Listen on `document`, not only on the button element.
* Make sure that checkout completed successfully for `shoppex:success`.
* Verify that browser extensions or policies do not block cross-origin frames.

### Modal appears behind custom UI

Use low-risk defaults:

* Avoid custom overlays with extreme z-index values.
* Test with your cookie or privacy banners and support widgets.

### Dynamic content buttons do not open checkout

The SDK auto-observes DOM changes, but make sure that:

* New nodes actually include `data-shoppex-product-id`, `data-shoppex-group-id`, or `data-shoppex-checkout`.
* Your frontend does not stop propagation on click before the SDK handler runs.

### Debug helpers

Simple runtime checks:

```javascript theme={"system"}
console.log('Shoppex available:', typeof window.Shoppex !== 'undefined');
```

```javascript theme={"system"}
document.addEventListener('shoppex:error', (event) => {
  console.error('Embed error:', event.detail.error);
});
```

<Card title="Integration patterns and framework snippets" icon="window-maximize" href="/developers/embeds/overview">
  Next.js, React, WordPress, and Webflow snippets, plus the hybrid product-cards pattern.
</Card>
