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

# Checkout Embed

> Open SellAuth Checkout in a modal on your own site.

## Integration Overview

The **Checkout Embed** opens SellAuth Checkout in a modal on top of your site. The buyer reviews
their cart, adjusts quantities, and pays without ever leaving your page.

The script is a thin wrapper: it builds a URL and opens an iframe. It pulls in no third party
dependencies and makes no network request before the modal opens, so the button responds instantly.

<Note>
  Already using the older `sellauth-embed-2.js`? It keeps working. See
  [Migrating from v2](#migrating-from-v2) when you are ready to switch.
</Note>

## Quick Start

Add the script to your page:

```html theme={null}
<script src="https://static.sellauth.com/embed/v3.min.js" defer></script>
```

Then add a button:

```html theme={null}
<button
  data-sellauth-shop="91082"
  data-sellauth-shop-url="https://yourstore.com"
  data-sellauth-cart='[{"productId":35665,"variantId":120121,"quantity":1}]'
>
  Buy Now
</button>
```

That is the whole integration. Any element carrying `data-sellauth-shop` opens the checkout when
clicked, including elements added to the page later by a framework.

<div className="not-prose my-6">
  <button className="inline-flex cursor-pointer items-center gap-2 rounded-lg border border-gray-200 bg-gray-900 px-4 py-2 text-sm font-medium text-white transition-opacity hover:opacity-90 dark:border-white/10 dark:bg-white dark:text-gray-900" data-sellauth-demo="{&#x22;shopId&#x22;:254659,&#x22;shopUrl&#x22;:&#x22;https://demo-marble.mysellauth.com&#x22;,&#x22;cart&#x22;:[{&#x22;productId&#x22;:801742,&#x22;variantId&#x22;:1369764,&#x22;quantity&#x22;:1}]}">
    Open the demo checkout
  </button>
</div>

That button is the real embed, running on this page against our demo store. Nothing is charged
until you reach the payment step.

<Tip>
  **You do not need a `variantId`.** Drop it and the modal shows a variant picker, so a single Buy
  Now button covers a product with any number of variants and you never build a size or plan
  selector yourself:

  ```html theme={null}
  <button
    data-sellauth-shop="91082"
    data-sellauth-shop-url="https://yourstore.com"
    data-sellauth-cart='[{"productId":35665,"quantity":1}]'
  >
    Buy Now
  </button>
  ```

  See [Letting Buyers Choose](#letting-buyers-choose).
</Tip>

## Required Values

<ParamField path="shopId" type="integer" required>
  Your shop ID, from the [API access page](https://dash.sellauth.com/api/).
</ParamField>

<ParamField path="shopUrl" type="string" required>
  Your store URL, for example `https://yourstore.com` or `https://yourshop.mysellauth.com`.
  Checkout is served from your own domain, so the embed needs to know it.
</ParamField>

<ParamField path="cart" type="array" required>
  One or more items. Only `productId` and `quantity` are required:

  * `{ productId, quantity }` lets the buyer pick the variant in the modal
  * `{ productId, variantId, quantity }` goes straight to that exact variant

  Both IDs are on the [Products page](https://dash.sellauth.com/products/). See
  [Letting Buyers Choose](#letting-buyers-choose).
</ParamField>

## JavaScript API

For anything beyond a static button, call `sellAuth.open()` directly:

```html theme={null}
<button onclick="sellAuth.open({
  shopId: 91082,
  shopUrl: 'https://yourstore.com',
  cart: [
    { productId: 35665, variantId: 120121, quantity: 1 },
    { productId: 35666, variantId: 27385, quantity: 2 }
  ],
  theme: 'dark'
})">Buy Now</button>
```

<CodeGroup>
  ```js React theme={null}
  export function BuyButton() {
    const buy = () =>
      window.sellAuth.open({
        shopId: 91082,
        shopUrl: 'https://yourstore.com',
        cart: [{ productId: 35665, variantId: 120121, quantity: 1 }],
      })

    return <button onClick={buy}>Buy Now</button>
  }
  ```

  ```jsx Next.js theme={null}
  import Script from 'next/script'

  export default function Page() {
    return (
      <>
        <Script src="https://static.sellauth.com/embed/v3.min.js" strategy="afterInteractive" />
        <button
          onClick={() =>
            window.sellAuth.open({
              shopId: 91082,
              shopUrl: 'https://yourstore.com',
              cart: [{ productId: 35665, variantId: 120121, quantity: 1 }],
            })
          }
        >
          Buy Now
        </button>
      </>
    )
  }
  ```
</CodeGroup>

<Note>
  React and Next.js need no special hook or component. The cart and payment handling all live
  inside the iframe, so `sellAuth.open()` is the entire integration.
</Note>

## Letting Buyers Choose

Pinning a `variantId` means your page has to ask which size, tier or duration the buyer wants
before the modal opens, so you end up building a variant selector, keeping its prices in step with
your dashboard, and hiding options that sell out.

Omit `variantId` and none of that is your problem. The modal shows the picker itself, always in
sync with your product:

```html theme={null}
<button
  data-sellauth-shop="91082"
  data-sellauth-shop-url="https://yourstore.com"
  data-sellauth-cart='[{"productId":35665,"quantity":1}]'
>
  Buy Now
</button>
```

<div className="not-prose my-6">
  <button className="inline-flex cursor-pointer items-center gap-2 rounded-lg border border-gray-200 bg-gray-900 px-4 py-2 text-sm font-medium text-white transition-opacity hover:opacity-90 dark:border-white/10 dark:bg-white dark:text-gray-900" data-sellauth-demo="{&#x22;shopId&#x22;:254659,&#x22;shopUrl&#x22;:&#x22;https://demo-marble.mysellauth.com&#x22;,&#x22;cart&#x22;:[{&#x22;productId&#x22;:801743,&#x22;quantity&#x22;:1}]}">
    Open the picker
  </button>
</div>

That is the same demo store, this time with no `variantId`, so the modal asks which one you want.

One button now covers a product with any number of variants, and adding a variant in your dashboard
needs no change to your site.

The picker lists each variant with its price, any sale price, its description and its stock. Sold
out variants cannot be selected. Once every open product has a variant, the buyer moves straight to
the cart and can still switch variant there.

A product with only one variant in stock is selected automatically, so the buyer never sees a
picker with a single choice.

You can mix both styles in one cart. Pin the variants you know and leave the rest open:

```js theme={null}
sellAuth.open({
  shopId: 91082,
  shopUrl: 'https://yourstore.com',
  cart: [
    { productId: 35665, variantId: 120121, quantity: 1 },
    { productId: 35666, quantity: 1 },
  ],
})
```

<Note>
  `skipCart` is ignored while any product is still missing a variant, since there is nothing to
  submit until the buyer has chosen.
</Note>

## Campaign Attribution

The embed also records where a buyer came from. When your page is opened with `utm_*` parameters, or
from another site, the script keeps the UTM values (or the referrer's host, for example `reddit.com`)
for the current visit and stamps them on the invoice. The dashboard shows them on the invoice, lets
you filter the invoice list by them, and includes them in exports.

Attribution is visit scoped, not a long lived cookie: it lives in `localStorage` on your domain
and slides forward for 30 minutes of browsing. A tagged landing overwrites what was stored, an
external referrer only fills an empty slot, and a plain visit a day later is not attributed to the
old campaign.

To set it yourself, pass the `attribution` option. To turn the automatic capture off, declare it
before the script loads:

```html theme={null}
<script>window.sellAuthConfig = { attributionCapture: false }</script>
<script src="https://static.sellauth.com/embed/v3.min.js" defer></script>
```

## Affiliate Tracking

The embed captures referrals for you. When someone lands on your page through an affiliate link, the
script reads `?ref`, `?aff` or `?affiliate` from the URL, remembers it, and attaches it to whatever
that visitor buys later.

```
https://yourstore.com/pricing?ref=alex
```

Nothing to wire up. The script is already on the page, and the code is sent with the order even if
the buyer browses around for a week before clicking Buy.

This has to happen in the script rather than in the checkout itself, because the checkout runs in a
frame that cannot read your page's URL. Sending no referrer is what keeps your traffic private from
us, and it means the embed is the only part of the integration that can see `?ref` at all.

### Where the code comes from

Three sources, in order. The first one that has a value wins.

<Steps>
  <Step title="The affiliate option">
    Set it yourself when you already know the referrer, for example from your own analytics or a
    logged in session.

    ```js theme={null}
    sellAuth.open({ shopId: 91082, shopUrl: 'https://yourstore.com', cart, affiliate: 'alex' })
    ```
  </Step>

  <Step title="The page URL">
    `?ref`, `?aff` or `?affiliate` on the current page.
  </Step>

  <Step title="The stored cookie">
    A code captured on an earlier visit.
  </Step>
</Steps>

### The cookie

Capturing a code writes `sa_aff` on your own domain: 30 days, `SameSite=Lax`, readable only by your
site. It is a first party cookie that you set, so if you run a cookie banner, it belongs in your
marketing or analytics category alongside your other attribution tags. The embedded checkout runs
in an iframe on your site and cannot see the storefront's own [consent banner](/guides/cookie-consent)
choice, so your page's banner is the one that governs it.

To turn capture off entirely, declare it before the script loads:

```html theme={null}
<script>window.sellAuthConfig = { affiliateCapture: false }</script>
<script src="https://static.sellauth.com/embed/v3.min.js" defer></script>
```

With capture off, the `affiliate` option still works. Only the URL reading and the cookie stop.

### Before it works

<Warning>
  A code that fails any of these is dropped silently. The order is still created, just with no
  affiliate attached, so test with a real code on a real account.
</Warning>

* **Your affiliate program has to be enabled** in your dashboard. Until it is, every code is
  ignored. See [Affiliates](/guides/affiliates).
* **Codes are at most 16 characters**, letters, numbers, underscores and hyphens. Anything longer or
  with other characters never leaves the browser.
* **Buyers cannot refer themselves.** A code is dropped when it belongs to the account buying with
  it, matched on email address. This one catches people out while testing, since your own code plus
  your own email looks exactly like a broken integration.

Commission rates, tiers, buyer discounts and payouts are all configured in your dashboard, not here.
The embed only carries the code.

## Options

<ParamField path="theme" type="string" default="auto">
  `auto`, `light`, or `dark`. `auto` follows the visitor's system preference. A shop that locks its
  checkout colour scheme overrides this.
</ParamField>

<ParamField path="modal" type="boolean" default="true">
  `false` opens checkout in a new tab instead of a modal.
</ParamField>

<ParamField path="skipCart" type="boolean" default="false">
  Skips the cart review screen and goes straight to payment. Use this when your own site already
  shows a cart. Quantities are no longer adjusted to available stock, so an item that is short is
  rejected rather than reduced.
</ParamField>

<ParamField path="closeButton" type="boolean" default="true">
  `false` hides the close button. Clicking the backdrop still closes the modal.
</ParamField>

<ParamField path="shopCard" type="boolean" default="false">
  Shows your shop logo and name at the top of the modal. Off by default, since your own site
  already establishes who the buyer is dealing with.
</ParamField>

<ParamField path="ticketPanel" type="boolean" default="false">
  Shows the support ticket panel after purchase. Off by default, because it links to your SellAuth
  storefront. Turn it on if you handle support through SellAuth tickets.
</ParamField>

<ParamField path="currency" type="string">
  Three letter currency code, for example `EUR`.
</ParamField>

<ParamField path="email" type="string">
  Prefills the buyer's email address.
</ParamField>

<ParamField path="affiliate" type="string">
  An affiliate code, at most 16 characters. Left unset, the embed picks up `?ref`, `?aff` or
  `?affiliate` from your page URL and remembers it for 30 days. See
  [Affiliate Tracking](#affiliate-tracking).
</ParamField>

<ParamField path="locale" type="string">
  Two letter language code. Defaults to your page's `<html lang>`.
</ParamField>

<ParamField path="metadata" type="object">
  Up to 10 custom key and value pairs stored on the invoice.
</ParamField>

<ParamField path="attribution" type="object">
  Campaign attribution stored on the invoice: `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`,
  `utm_content` and `referrer_host`, each at most 100 characters. Left unset, the embed uses what it
  captured from your page URL and referrer. See [Campaign Attribution](#campaign-attribution).
</ParamField>

<ParamField path="returnUrl" type="string">
  Where to send the buyer after a completed purchase.
</ParamField>

<ParamField path="scrollTop" type="boolean" default="true">
  Scrolls your page to the top when the modal opens.
</ParamField>

<ParamField path="newTab" type="boolean" default="true">
  With `modal: false`, opens a new tab rather than navigating the current one.
</ParamField>

Every option has a matching data attribute: `theme` becomes `data-sellauth-theme`, `shopCard`
becomes `data-sellauth-shop-card`, and so on.

## Checkout Settings

The modal runs your shop's checkout, so what appears inside it also comes from
[**Settings → Checkout**](https://dash.sellauth.com/shop#checkout):

* Show Coupon Code Textbox
* Show Terms Checkbox
* Pre-check Terms Checkbox
* Show Newsletter Checkbox
* Show Immediate Delivery Checkbox
* Show Withdrawal Form

Turning one off removes it from the embed and from your SellAuth storefront checkout, since both
read the same settings. That matters if you sell on both, and rarely does if the embed is your only
checkout. See [How checkout works](/guides/checkout#configuring-the-page) for what each one does.

## Events

The embed reports what happens inside the modal, which is how you track conversions from a checkout
that lives in an iframe.

```js theme={null}
document.addEventListener('sellauth:success', (event) => {
  gtag('event', 'purchase', {
    transaction_id: event.detail.invoiceId,
    value: event.detail.total,
    currency: event.detail.currency,
  })
})
```

| Event | Fires when | `event.detail` |
| - | - | - |
| `sellauth:opened` | The modal opens | `{ url }` |
| `sellauth:ready` | Checkout has loaded | |
| `sellauth:invoice-created` | The buyer continues to payment | `{ invoiceId }` |
| `sellauth:success` | Payment completes | `{ invoiceId, total, currency }` |
| `sellauth:error` | Something fails | `{ code, message }` |
| `sellauth:close` | The modal closes | `{ reason }` |

Callbacks work too, if you prefer them per call:

```js theme={null}
sellAuth.open({
  shopId: 91082,
  shopUrl: 'https://yourstore.com',
  cart: [{ productId: 35665, variantId: 120121, quantity: 1 }],
  onSuccess: ({ invoiceId }) => console.log('paid', invoiceId),
})
```

<Warning>
  With `modal: false`, checkout runs in a separate tab and cannot report back. Only
  `sellauth:opened` fires. Use the modal if you need conversion events.
</Warning>

## Methods

| Method | Purpose |
| - | - |
| `sellAuth.open(options)` | Opens checkout. Returns `{ close() }`. |
| `sellAuth.close()` | Closes the modal. |
| `sellAuth.on(type, callback)` | Subscribes to an event. |
| `sellAuth.off(type, callback)` | Unsubscribes. |
| `sellAuth.version` | The script version. |

## Pinning a Version

The URL above tracks the latest v3 release and picks up fixes automatically. To pin an exact build,
including for a Subresource Integrity hash, use the full version:

```html theme={null}
<script
  src="https://static.sellauth.com/embed/v3.2.0.min.js"
  integrity="sha384-Bs5zq22vHL1b6cTCQrrtMYnj6VyjvrW2aN7tFby8LG6yurpUKzbrc2Gdj/h7ExmF"
  crossorigin="anonymous"
  defer
></script>
```

Pinned URLs never change once published. The `v3.min.js` alias is updated in place with backward
compatible releases, so it cannot be used with an integrity hash.

## Content Security Policy

If your site sends a CSP, allow the script and the frame:

```
script-src https://static.sellauth.com;
frame-src https://yourstore.com;
```

`frame-src` must list your own store domain, since that is where checkout is served from. Without
it the modal stays blank, and the embed falls back to opening checkout in a new tab.

Pass a nonce if your policy requires one:

```html theme={null}
<script>window.sellAuthConfig = { nonce: 'YOUR_NONCE' }</script>
```

## Migrating from v2

`sellauth-embed-2.js` keeps working and will not be removed. When you switch:

<Steps>
  <Step title="Change the script URL">
    Replace `https://sellauth.com/assets/js/sellauth-embed-2.js` with
    `https://static.sellauth.com/embed/v3.min.js`.
  </Step>

  <Step title="Add your store URL">
    v3 needs `shopUrl` alongside `shopId`. This is the one required change to your calls.
  </Step>

  <Step title="Switch to sellAuth.open()">
    Replace `window.sellAuthEmbed.checkout(this, {...})` with `sellAuth.open({...})`. Option names
    are otherwise unchanged.
  </Step>
</Steps>

What you gain:

* Nothing extra loaded into your page, so no CSP conflicts and nothing for ad blockers to break.
* Conversion events, which v2 had no way to report.
* A cart review step where buyers can adjust quantities before paying.
* No copy and paste React hook. `sellAuth.open()` is the whole integration.
* On iOS, `modal: false` opens a new tab instead of navigating away from your page.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.