Skip to main content
This page documents the current behavior of the Checkout Embed SDK (window.Shoppex): how the script is served, every snippet parameter, the full JavaScript API, every event the embed emits, the Content Security Policy your site needs, and the security model. Everything here is derived from the shipped SDK. Where a parameter exists but does nothing on a given path, this page says so instead of listing it as supported.

The script

The script defines one global, window.Shoppex, and initializes itself on DOMContentLoaded. You do not need to call Shoppex.init() unless you pass configuration.

Versioning and integrity

Do not add an integrity attribute to the embed script tag today. Subresource Integrity on a cross-origin script also requires crossorigin="anonymous", and the browser then requires a CORS response header that this URL does not send. The script would be blocked outright, and your checkout button would stop working. A pinned hash would also break at the next checkout deploy, because the URL is mutable by design.
If you audit third-party scripts, take the digest as a record rather than an enforcement mechanism:
Immutable versioned delivery, which is what makes SRI pinning meaningful, is tracked in Future work.

Self-hosting

Do not copy the script to your own domain. It must be served from the same origin as the checkout iframe: the SDK validates every incoming message against the checkout origin, and a self-hosted copy pointed at a different origin will silently drop every event.

Integration modes

The SDK binds click handlers to any element matching one of these selectors, including elements added to the DOM later (a MutationObserver rebinds automatically):
  • [data-shoppex-product-id]
  • [data-shoppex-group-id]
  • [data-shoppex-checkout]
  • [data-shoppex-add-to-cart]
  • [data-shoppex-cart-toggle]
  • [data-shoppex-cart-checkout]
  • [data-shoppex-widget]
Single product:
Multiple items in one checkout:
Product group:

Snippet parameters

Every attribute below has a matching Shoppex.open() option, listed in the last column.

Identity

Per item

Checkout behavior

Rewards

Prebuilt widgets

data-shoppex-widget renders a small UI inside a shadow root on the host element, so you do not have to write button markup.
Widget styles are injected into the widget’s shadow root without a CSP nonce. Under a strict style-src that has no 'unsafe-inline', the widget renders unstyled. If you enforce a strict style policy, write your own markup and put data-shoppex-product-id on your own button instead.
data-shoppex-gateway is an instruction with a price consequence, not a hint. A preselected gateway clears the “buyer must pick a method” step, which commits the invoice to that method and its merchant-configured fee without the buyer clicking anything. A key your shop does not offer is ignored, and the buyer picks as usual. On an add-to-cart button the attribute does nothing — put it on the data-shoppex-cart-checkout button.

Metadata

metadata lands on the invoice as custom_fields on both checkout paths. How it travels differs, but the result is the same: Either way it is readable on the invoice and through the Developer API.
data-shoppex-custom-fields reaches the same place and stays the right attribute for per-item values on a single product or on one line of data-shoppex-items. data-shoppex-metadata is button-wide. If both set the same key on a single-product checkout, the data-shoppex-custom-fields value wins, because it is the more specific of the two. Only data-shoppex-custom-fields can answer a field for the buyer. data-shoppex-metadata never hides a field and is never submitted as a buyer’s answer: if a metadata key happens to have the same name as one of the product’s own custom fields, the field is still shown, the buyer’s answer is what lands on the invoice under that key, and the metadata value is dropped with a console warning. Prefill the field with data-shoppex-custom-fields, or rename the metadata key, if you meant to set it. Some keys are reserved for Shoppex invoice state and are refused, whether they arrive through data-shoppex-custom-fields or data-shoppex-metadata: license_uid, custom_fields, delivery_instructions, custom_field_definitions, payment_method_terms_accepted, payment_method_terms_accepted_at, discord_integration, and anything starting with billing_, payment_link_, payment_method_, affiliate_, reseller_, supplier_, or _import. data-shoppex-custom-fields keys are refused twice — the SDK keeps them out of the checkout URL, and the checkout drops them again on arrival, because that URL is buyer-editable and shareable. Rejected keys log a console warning. source and campaign are explicitly merchant-owned and always allowed.

JavaScript API

Checkout

Shoppex.init(config?)

Binds elements and keyboard handlers. Called automatically on load, so you only need it to pass configuration. Safe to call repeatedly; later calls merge into the existing config.
shopId and locale act as defaults for every call. nonce is required under a strict style-src — see Content Security Policy. checkoutBaseUrl and apiBaseUrl exist for local development and are validated as http(s); an invalid value logs a warning and falls back to production.

Shoppex.open(options)

Opens the checkout modal. Needs at least one item with a non-empty productId, or a groupId; otherwise it logs a warning and does nothing. Opening while a modal is already open closes the previous one first. More than one item routes through cart checkout, which creates the invoice before the iframe opens and therefore requires shopId.

Shoppex.close()

Closes the modal. Emits shoppex:close.

Cart

Every cart method takes an optional { shopId } and returns the resulting item array. Each one also emits shoppex:cart-change.

Events

Every public event is a CustomEvent dispatched on document. They bubble and cross shadow boundaries, so one listener on document sees all of them.

Checkout lifecycle

Abandonment tracking, end to end:

Cart

Rewards

rewards carries summary, activity, earned_after_invoice, and pending_after_invoice.

Locale

Diagnostics

The underlying postMessage protocol

You normally never touch this: the SDK validates the sender and re-dispatches everything as the document events above. It is documented so you can recognise the traffic in a debugger. Messages are { version: "1", type, payload } and travel from the checkout iframe to your page, addressed to your exact origin when the checkout can determine it. Message types: shoppex:ready, shoppex:resize, shoppex:close, shoppex:success, shoppex:error, shoppex:invoice-created, shoppex:redirect, shoppex:external-redirect, shoppex:rewards-updated, shoppex:rewards-applied, shoppex:rewards-error, shoppex:setLocale, and shoppex:style-update (used by the dashboard Style Center preview, never by a live embed). shoppex:resize and shoppex:style-update are consumed by the SDK to size and theme the modal, and have no document event. shoppex:external-redirect reaches your page as shoppex:redirect.

Content Security Policy

If your site sends a Content-Security-Policy header, the embed needs these directives. Each one is required by a specific thing the SDK does on your page. A complete working policy:
Notes that save an afternoon of debugging:
  • default-src does not cover frame-src on its own in every browser configuration you will meet. Name frame-src explicitly.
  • Omitting connect-src looks like a styling bug. Your checkout styling silently falls back to defaults, and cart checkout fails with a generic error, because both are fetch calls from your page.
  • Omitting style-src leaves the modal open but unstyled.
  • Payment provider popups are windows, not frames. They are not covered by frame-src, and they need no CSP entry.
  • If you override checkoutBaseUrl or apiBaseUrl for a staging environment, use those origins in the policy instead.
  • Test the policy on the production domain. A local dev server usually sends no CSP at all, so violations only appear once you deploy.

Referrer policy

The checkout addresses its messages to your exact origin, which it learns from the referrer of the iframe request. Under Referrer-Policy: no-referrer it cannot, and falls back to a wildcard target origin. The SDK still validates the sender, so the embed keeps working, but the browser’s default strict-origin-when-cross-origin gives you the tighter behavior. Keep it.

Security model

The SDK validates the origin of every incoming message before acting on it, and — once the iframe window is known — that the message came from that window. A forged postMessage from another origin or another frame is ignored.
Trusted origins are https://checkout.shoppex.io plus the local checkout development origins. Once a modal is open, the SDK pins to that iframe’s exact origin. Redirects are checked by kind, because they carry different trust:
  • In-checkout redirects must stay on the checkout origin.
  • External payment redirects must be http(s) and open in a popup, never as a top-window navigation. They intentionally allow arbitrary hosts, because a custom manual gateway legitimately redirects to its own payment host. The trust boundary is the verified iframe origin that sent the URL.
  • Post-purchase merchant redirects may navigate the top window only to your own origin over HTTPS.
Popups sever window.opener before navigating. If a popup is blocked, the SDK navigates the current tab instead rather than stranding the buyer. Merchant custom CSS is sanitized in the SDK before it reaches the DOM — @import, expression(), behavior:, and javascript:/vbscript:/data: URLs are stripped — on top of authoritative server-side validation.

Return URL safety

Never pass unvalidated user input as returnUrl. Treat it as an open-redirect surface and use a hardcoded or server-validated URL.

Production checklist

1

Serve your page over HTTPS

The modal runs in a cross-origin iframe, and browsers block mixed content.
2

Load the script from checkout.shoppex.io

Never self-host or proxy it. It has to match the iframe origin or every event is dropped.
3

Set shopId once

Shoppex.init({ shopId }) saves repeating it on every button, and cart and multi-item checkout require it.
4

Handle success, error, and close

shoppex:success for conversions, shoppex:error for failures, shoppex:close with completed: false for abandonment.
5

Validate your CSP on the production domain

Especially connect-src, whose absence looks like a styling bug rather than a policy violation.
6

Check dark mode and mobile

The modal follows prefers-color-scheme and switches to a sheet layout on small screens.
7

Verify product, variant, and group IDs

A wrong ID does not open the modal. Watch the console.

Troubleshooting

The modal does not open

  • The script loaded and window.Shoppex exists.
  • The element carries a valid data-shoppex-product-id, data-shoppex-group-id, or data-shoppex-items.
  • Your click handler does not call stopPropagation() before the SDK’s handler runs.
  • The console shows no [Shoppex] warning. Every rejected configuration logs one.

The modal opens but stays empty

Almost always CSP. Check the console for a frame-src violation, then for connect-src.

Multi-item or cart checkout fails immediately

It needs shopId, and it calls the API from your page. Check connect-src, then the shoppex:error detail for the API’s message.

Events do not fire

  • Listen on document, not on the button.
  • The SDK only listens while a modal is open. Register your listeners once, at load.

Styling looks wrong

  • Under a strict CSP, pass nonce to Shoppex.init().
  • Your checkout styling is fetched from the API. If connect-src blocks it, the modal falls back to default styling.

Debug helpers

Future work

These are known gaps. They are listed here so integrations are built against what exists rather than what is assumed. Immutable versioned script URL with SRI. Real SRI pinning needs two things this URL does not have: a version segment whose content never changes, and an Access-Control-Allow-Origin header so a cross-origin integrity check can run at all. Shoppex already delivers its storefront commerce runtime this way — content-addressed, immutable per build, with an auto-injected integrity attribute — so the pattern is proven in-house and the embed script can follow it. Until then, the digest above is a record, not an enforcement mechanism. Affiliate attribution beyond the invoice. affiliateCode and referralCode travel with the checkout and land on the invoice. Attribution that survives across sessions — a first-touch cookie, a click identifier, or a referral captured on one page and honored on a purchase made later — needs backend persistence that does not exist yet. Reward events the checkout does not emit. shoppex:rewards-applied and shoppex:rewards-error are part of the protocol and are re-dispatched by the SDK, but the checkout never sends them. They will start firing without any change on your side. Explicit parent origin. The checkout learns your origin from the iframe request’s referrer. Passing it explicitly as a query parameter — which the protocol already supports on the receiving side — would make message targeting exact regardless of your referrer policy. Nonced widget styles. Styles for data-shoppex-widget are injected without the CSP nonce, so the prebuilt widgets need 'unsafe-inline' in style-src. The modal itself is fully nonce-aware.

Integration patterns and framework snippets

Next.js, React, WordPress, and Webflow snippets, plus the hybrid product-cards pattern.