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
window.Shoppex, and initializes itself on DOMContentLoaded. You do not need to call Shoppex.init() unless you pass configuration.
Versioning and integrity
If you audit third-party scripts, take the digest as a record rather than an enforcement mechanism:
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
- Declarative (data attributes)
- Programmatic (JS API)
- Cart (add to cart, then check out)
The SDK binds click handlers to any element matching one of these selectors, including elements added to the DOM later (a Multiple items in one checkout:Product group:
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]
Snippet parameters
Every attribute below has a matchingShoppex.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.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 aCustomEvent 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 aContent-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:
default-srcdoes not coverframe-srcon its own in every browser configuration you will meet. Nameframe-srcexplicitly.- Omitting
connect-srclooks like a styling bug. Your checkout styling silently falls back to defaults, and cart checkout fails with a generic error, because both arefetchcalls from your page. - Omitting
style-srcleaves 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
checkoutBaseUrlorapiBaseUrlfor 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. UnderReferrer-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.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.
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
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.Shoppexexists. - The element carries a valid
data-shoppex-product-id,data-shoppex-group-id, ordata-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 aframe-src violation, then for connect-src.
Multi-item or cart checkout fails immediately
It needsshopId, 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
noncetoShoppex.init(). - Your checkout styling is fetched from the API. If
connect-srcblocks 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 anAccess-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.