Embed

Hosted checkout, Direct Debit capture, and Cart on your website

Developer preview. Set up Embed in Dashboard > Commerce > Embed: issue a Cart publishable key (rk_pk_*), register the exact origin of your site, and copy the snippet. Test a full purchase on your own site before you rely on checkout.js in production.

Slug-only Cart snippets stop working after 6 October 2026

Until the end of 6 October 2026 (UTC), the Cart drawer still accepts a snippet that names your merchant slug without a publishable key, as long as the page's origin is registered. After that date those requests are refused. If your snippet has no data-revkeen-publishable-key, follow Migrating slug-only snippets now. Hosted checkout and Direct Debit capture are not affected.

The CDN bundle at https://releases.revkeen.com/embed/v1/checkout.js provides hosted checkout in an iframe, Direct Debit capture, and a Cart drawer. Card details remain inside the hosted payment surface; your site receives no card PAN.

Serve the script from the RevKeen origin. Register the exact HTTPS origin of your storefront in checkout settings before embedding. A blocked frame-ancestors response means the origin has not been allowed; opening the hosted checkout URL directly is the fallback.

Hosted checkout

Create a Checkout Session on your server, then pass its returned session token to the browser:

<div id="checkout"></div>
<script src="https://releases.revkeen.com/embed/v1/checkout.js"></script>
<script>
  const token = window.checkoutSessionToken; // Returned by your authenticated server.
  if (!token) throw new Error("Create a checkout session first");
  const checkout = RevKeenEmbed.mountCheckout("#checkout", {
    sessionToken: token,
    onReady: () => console.log("Checkout ready"),
    onResize: (height) => console.log("Checkout height", height),
  });
  // Call checkout.unmount() when removing this screen in a single-page app.
</script>

The embed loads /p/{sessionToken}?embed=true, enables payment permissions on the iframe, and checks the message origin and source before invoking callbacks. onReady and onResize are emitted today. Although the options include onSuccess and onError, the checkout bridge does not yet emit those outcomes. Confirm payment on your server using verified webhooks, never a browser callback or redirect alone.

Automatic mount uses data-revkeen-checkout and data-session-token on the container. An explicit mount is easier when your application creates the session asynchronously. Payment-method availability follows the wallet eligibility rules, browser support, and merchant configuration.

Direct Debit capture

Auto-mount:

<div
  data-revkeen-dd
  data-merchant-slug="acme"
  data-merchant-id="..."
  data-api-base-url="https://checkout.revkeen.com"
></div>
<script src="https://releases.revkeen.com/embed/v1/checkout.js" async></script>

Programmatic mount:

<div id="dd"></div>
<script src="https://releases.revkeen.com/embed/v1/checkout.js"></script>
<script>
  const instance = RevKeenEmbed.mountDD("#dd", {
    merchantSlug: "acme",
    merchantId: "...",
    apiBaseUrl: "https://checkout.revkeen.com",
    onSuccess: (mandate) => console.log("mandate", mandate.mandateRef),
  });
</script>

merchantSlug is required. apiBaseUrl defaults to https://checkout.revkeen.com, which serves /api/dd/*. Those endpoints enforce a CORS allow-list of approved embed origins. Bank details are not persisted in the browser.

Cart drawer

Cart products are priced server-side. The storefront sends product/price references only, never money fields. Convert still goes to hosted checkout at /p/{sessionToken}.

The drawer authenticates with a publishable key (rk_pk_*) and must run on an origin registered for that merchant (/v2/storefront/origins). This is the same fail-closed browser model as /v2/storefront/*. Do not put a secret key (rk_live_* / rk_sandbox_*) in the page.

<button
  class="revkeen-add-to-cart"
  data-revkeen-product-id="11111111-1111-1111-1111-111111111111"
>
  Add to cart
</button>
<button data-revkeen-cart>Cart</button>
<script
  src="https://releases.revkeen.com/embed/v1/checkout.js"
  async
  data-revkeen-cart
  data-revkeen-publishable-key="rk_pk_live_..."
  data-revkeen-merchant-id="your-merchant-id"
  data-revkeen-merchant-name="Acme"
  data-revkeen-floating-button
></script>

The easiest way to get this snippet is Commerce > Embed in the dashboard: it fills in your merchant id and your publishable key. A key issued before 30 September 2026 is not stored, so rotate it once to have it filled in. If you write it by hand, data-revkeen-merchant-id (or data-revkeen-merchant-slug) must name your own merchant. An unknown merchant is refused with 404, and one the key does not belong to with 403.

revkeen-checkout-link with a product id creates a one-product cart, converts server-side, and redirects to hosted checkout. A revkeen-checkout-link anchor with no product id is left as an ordinary link to its href; the script only warms the connection to it.

Trigger attributes

Add-to-cart and buy-now triggers read these attributes from the element itself:

AttributeRequiredMeaning
data-revkeen-product-idYesThe RevKeen product to add. data-revkeen-add="<product id>" is an equivalent shorthand
data-revkeen-price-idNoA specific price of that product. Without it, the product's default price is used
data-revkeen-quantityNoA whole number of 1 or more. Anything else is treated as 1

These are references only. The price, name and billing cadence are always resolved on the server, so a changed attribute cannot change what checkout charges.

The script binds triggers once, when the page has loaded. A button that your site adds later - for example a product list loaded by another script after the page renders - is not bound automatically. For those, load the script without the data-revkeen-cart attributes (so it does not mount a second drawer of its own), mount the cart once yourself, and call it from your own click handler:

<script src="https://releases.revkeen.com/embed/v1/checkout.js"></script>
<script>
  const cart = RevKeenEmbed.mountCart({
    publishableKey: "rk_pk_live_...",
    merchantSlug: "acme",
  });
  document.addEventListener("click", (event) => {
    const button = event.target.closest("[data-revkeen-product-id]");
    if (!button) return;
    event.preventDefault();
    cart.addItem({ productId: button.dataset.revkeenProductId });
  });
</script>

Use either the automatic binding or your own handler for a given button, not both, or one click adds the item twice.

Migrating slug-only snippets

Until the end of 6 October 2026 (UTC), Cart requests (create, change and convert) still accept a merchant slug without a publishable key if the browser Origin is already registered for that merchant. Those responses carry Deprecation and Sunset headers so you can find affected pages. After that date a request without a valid publishable key is refused. To migrate:

  1. Issue a Cart publishable key (rk_pk_*).
  2. Register the exact storefront origin (https://shop.example.com) via /v2/storefront/origins or revkeen cart origins add.
  3. Add data-revkeen-publishable-key (or publishableKey on mountCart). Keep the merchant id or slug, and make sure it is your own: it is checked against the key's merchant, and an unknown one is refused.

A missing or invalid rk_pk_* key, a secret key, or an unregistered Origin is refused. Server Cart Sessions (/v2/cart-sessions with a secret key) are unchanged.

Adding the snippet to your platform

The script tag is the same everywhere. What differs is where each platform lets you inject it, and which published origin you register.

Load checkout.js once, on every page that shows a cart trigger or a checkout container. Place it at the end of the document body so your product markup exists before the script binds to it.

PlatformWhere to pasteNotes
WordPressA code-snippets plugin, or wp_enqueue_script in a child theme's functions.phpDo not paste into a parent theme file - a theme update overwrites it
SquarespaceSettings > Advanced > Code Injection > FooterCode injection requires a Business plan or above
WixSettings > Custom Code > Add Custom Code, applied to all pages, placed in Body - end
WebflowProject Settings > Custom Code > Footer Code, then publishCustom code runs on published sites only, not in the Designer preview
Static HTMLImmediately before </body> on each page

Registering the right origin

Register the origin your visitors actually see, not your admin or build URL. Origins are exact: scheme://host[:port], lowercased, with no path, no trailing slash and no wildcards. Three consequences worth checking before launch:

  • https://example.com and https://www.example.com are two separate registrations. Register whichever your site canonically serves, and both if it answers on both.
  • A staging or preview domain - .webflow.io, .squarespace.com, a Wix preview host - is its own origin. Register it to test, then register the custom domain before you launch.
  • http:// is refused except for localhost and 127.0.0.1.

Add origins in Dashboard > Commerce > Embed, or with revkeen cart origins add.

The same page issues the Cart publishable key. It is shown once, when it is created or rotated. Copy it into your site builder then; if you lose it, rotate it and update every page that uses the old key.

Lifecycle and configuration

Entry pointRequired inputReturned controls
mountCheckout(target, options)Session tokenunmount()
mountDD(target, options)Merchant slugunmount()
mountDDDrawer(options)Merchant slugopen(), close(), unmount()
mountCart(options)Publishable key (rk_pk_*) plus merchant ID or slugDrawer controls, addItem, checkoutItem, getCart, getCount, subscribeCount, attachTrigger, mountCartPage
mountCartPage(target, options)The mountCart options, plus an optional continueShoppingUrlThe same controls as mountCart; unmount() removes the page and the drawer

Cart supports drawerPosition, floatingButton, floatingButtonLabel, merchant name/logo, terms/privacy links, and onCartChange, onCheckout, onError. Returning false from onCheckout suppresses the default redirect. A full cart page renders the same cart as the drawer: call cart.mountCartPage(element, { continueShoppingUrl }) on your page's cart element, and set cartPageUrl so the drawer shows a "View full cart" link. The cart page never puts the cart token in a URL. subscribeCount calls the listener immediately and returns an unsubscribe function; attachTrigger returns a detach function. Clean both up before unmounting. apiBaseUrl on Cart is the Engine origin; on hosted checkout it is the checkout origin. Do not interchange them.

For staging, use the matching staging checkout/Engine origins and staging session. Never combine a production token with a staging origin. Test keyboard focus, Escape/close behavior, resize, and a denied origin before enabling your storefront.

Troubleshooting

Start at Dashboard > Apps > Marketplace > Cart, under Integration health. It reports Cart keys, registered origins, priced products and webhook endpoints, and offers the fix inline for keys and origins.

What you seeCauseFix
The drawer says "The cart isn't available right now. Store owner: ..."A setup problem the Cart can name: the site isn't approved, the key is missing or invalid, or the Cart is turned offFollow the store-owner sentence. The same hint is logged once in the browser console, starting [RevKeenEmbed] RevKeen Cart setup:
Blank space where checkout should render; the console reports a frame-ancestors or "refused to connect" errorThe browser origin is not registeredRegister the exact origin, then hard-reload
401 or 403 on create, mutate or convertMissing, invalid, or secret key used in the browserSend the publishable key (rk_pk_*). A secret key in the browser is refused by design
403 with code origin_not_allowed, or the console reports a CORS error on a request to the RevKeen APIThe page's exact origin is not registered for the merchant that owns the publishable keyRegister the origin shown in the browser's address bar - including www. or the .webflow.io host - then hard-reload. A registration on another merchant, or for a different host, does not count
404 Merchant not found on every Cart callThe snippet names a merchant slug or id that does not exist, for example a placeholder left in a hand-written snippetCopy the snippet again from Commerce > Embed, which fills in your merchant id
403 merchant_mismatchThe merchant id or slug in the snippet is not the merchant that owns the publishable keyUse the snippet from the same account that issued the key
403 origin_not_allowed saying a publishable key can only be used from a browserThe publishable key was sent from a server, script or tool with no Origin headerUse a secret key for server-side calls; keep rk_pk_* in browser pages only
Cart responses carry Deprecation and Sunset headersThe snippet has no publishable key and is running on the slug-only grace window, which ends on 6 October 2026Follow Migrating slug-only snippets before that date
401 with PUBLISHABLE_KEY_REQUIRED on every Cart callThe snippet has no publishable key and the slug-only grace window has endedFollow Migrating slug-only snippets
The cart drawer never opens and no network calls are madeThe script ran before your markup existed, or the trigger has no matching attributeLoad the script at the end of the body; confirm data-revkeen-product-id is present
Checkout mounts, but onSuccess never firesThe checkout bridge does not emit that outcome yetConfirm payment server-side from webhooks
A product never appears in the cartThe product is inactive or has no priceIntegration health reports priced products; price the product, then retry

Never treat a browser callback or a redirect as confirmation of payment. Verified webhooks are the only settlement signal.

Owner: david · Reviewed 30 Sep 2026 · Next review 30 Oct 2026

On this page