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:
| Attribute | Required | Meaning |
|---|---|---|
data-revkeen-product-id | Yes | The RevKeen product to add. data-revkeen-add="<product id>" is an equivalent shorthand |
data-revkeen-price-id | No | A specific price of that product. Without it, the product's default price is used |
data-revkeen-quantity | No | A 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:
- Issue a Cart publishable key (
rk_pk_*). - Register the exact storefront origin (
https://shop.example.com) via/v2/storefront/originsorrevkeen cart origins add. - Add
data-revkeen-publishable-key(orpublishableKeyonmountCart). 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.
| Platform | Where to paste | Notes |
|---|---|---|
| WordPress | A code-snippets plugin, or wp_enqueue_script in a child theme's functions.php | Do not paste into a parent theme file - a theme update overwrites it |
| Squarespace | Settings > Advanced > Code Injection > Footer | Code injection requires a Business plan or above |
| Wix | Settings > Custom Code > Add Custom Code, applied to all pages, placed in Body - end | |
| Webflow | Project Settings > Custom Code > Footer Code, then publish | Custom code runs on published sites only, not in the Designer preview |
| Static HTML | Immediately 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.comandhttps://www.example.comare 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 forlocalhostand127.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 point | Required input | Returned controls |
|---|---|---|
mountCheckout(target, options) | Session token | unmount() |
mountDD(target, options) | Merchant slug | unmount() |
mountDDDrawer(options) | Merchant slug | open(), close(), unmount() |
mountCart(options) | Publishable key (rk_pk_*) plus merchant ID or slug | Drawer controls, addItem, checkoutItem, getCart, getCount, subscribeCount, attachTrigger, mountCartPage |
mountCartPage(target, options) | The mountCart options, plus an optional continueShoppingUrl | The 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 see | Cause | Fix |
|---|---|---|
| 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 off | Follow 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" error | The browser origin is not registered | Register the exact origin, then hard-reload |
401 or 403 on create, mutate or convert | Missing, invalid, or secret key used in the browser | Send 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 API | The page's exact origin is not registered for the merchant that owns the publishable key | Register 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 call | The snippet names a merchant slug or id that does not exist, for example a placeholder left in a hand-written snippet | Copy the snippet again from Commerce > Embed, which fills in your merchant id |
403 merchant_mismatch | The merchant id or slug in the snippet is not the merchant that owns the publishable key | Use the snippet from the same account that issued the key |
403 origin_not_allowed saying a publishable key can only be used from a browser | The publishable key was sent from a server, script or tool with no Origin header | Use a secret key for server-side calls; keep rk_pk_* in browser pages only |
Cart responses carry Deprecation and Sunset headers | The snippet has no publishable key and is running on the slug-only grace window, which ends on 6 October 2026 | Follow Migrating slug-only snippets before that date |
401 with PUBLISHABLE_KEY_REQUIRED on every Cart call | The snippet has no publishable key and the slug-only grace window has ended | Follow Migrating slug-only snippets |
| The cart drawer never opens and no network calls are made | The script ran before your markup existed, or the trigger has no matching attribute | Load the script at the end of the body; confirm data-revkeen-product-id is present |
Checkout mounts, but onSuccess never fires | The checkout bridge does not emit that outcome yet | Confirm payment server-side from webhooks |
| A product never appears in the cart | The product is inactive or has no price | Integration 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.
Related
Owner: david · Reviewed 30 Sep 2026 · Next review 30 Oct 2026