Pricing cards & snippet
A pricing card embeds a plan's tiers — each tier's title, price, cadence selector,
feature list, and a buy button — in a sandboxed iframe on your own page. It is the
/embed/<vanity> surface, addressed by the plan's public vanity slug.
The snippet
Drop the iframe plus embed.js on your page. embed.js auto-resizes the frame and
runs the bind / catalog handshakes; the cards render from the tenant's portal origin.
Code
Generate it with the ids filled in at Settings → Portal → Embedding (snippet type
Pricing cards) rather than hand-building the URL. <vanity> is the plan's public
slug; the query parameters below tune language, theme,
and where the buy CTA opens.
What the buyer sees
The public pricing card is anonymous. Every visitor sees the same cards and the
same buy CTA, and clicking it starts checkout — an anonymous magic-link checkout, or
a bound checkout when your page registers a fetchBindToken provider (see
Bind to a customer). The card carries no
buyer identity, so it never shows a current plan or upgrade/downgrade options.
To show View details / Upgrade / Downgrade in place of the buy CTA — for a buyer
you have already identified — embed the gated by-id card and resolve it with that
buyer's customerId. The customer-bound catalog token lets the card resolve the
buyer's owned tiers and render the affordances the logged-in portal shows. See
Gated (non-public) plans → Returning buyers.
Deep-link to one item
Drop the buyer straight onto one tier's checkout while keeping the cards behind it
(Back returns to them) by adding ?item=<planItemId> to the card URL. Always in-frame.
Code
Forced single-item checkout
Show only one tier's checkout — no cards, no Back — on the dedicated checkout route. Always in-frame; needs the operator's "Allow in-frame checkout" on.
Code
Custom call-to-action cards ("talk to sales")
A plan's card grid can include a custom call-to-action card — the operator's
"talk to sales" or Enterprise tier. In the catalogue it is the item whose
itemType is "CUSTOM_CTA". It renders as a normal pricing card, with its own
icon, features, and button label, and is visually indistinguishable from a priced
tier. It shows no price, its button navigates to an operator-set link instead of
starting checkout, and it always renders after every priced card in the grid.
The link (ctaLink) is an absolute https:// URL, a site-relative path
(/contact), a mailto: address, or a tel: number. Clicking the button does
not start checkout and emits no checkout lifecycle event — there is no
checkoutInitiated or checkoutConfirmed, since it is not a checkout. The button
respects the iframe target param: target=_top navigates your host page to the
link; any other target opens the link in a new tab so your embedding page stays in
place. A mailto: or tel: link always hands off to the OS handler.
Set ctaCallback=1 on the iframe to make a custom CTA button hand the click to
your page instead of following its link. The button then fires the
customCtaClicked event and navigates nothing, so your page owns the action —
open a chat widget, a contact modal, or a calendar. The event carries the operator
ctaLink, which your handler can navigate to or ignore. This is independent of
target, so a host can intercept CTA buttons while still using any checkout mode.
A CUSTOM_CTA item in the backend catalogue read carries itemType: "CUSTOM_CTA", its ctaLink, a ctaPresetKey ("contactUs", "bookDemo", or
"scheduleTime", derived from the button label for analytics), and priceMinor: null with sourceSubscriptionId: null. Treat these items as non-purchasable
wherever your code expects a buyable card.
When you set ctaCallback=1, register a listener so your page owns the click:
Code
The full e.detail payload (and every other embed event) is in
Lifecycle events.
Snippet query parameters
| Param | Values | Default | Meaning |
|---|---|---|---|
lang | en | da | en | All surfaces. Language the cards + checkout render in. |
theme | system | light | dark | system | All surfaces. Color theme. system follows the visitor's OS preference. |
target | inline | _blank | _top | callback | inline | Pricing card only. Where the buy CTA opens checkout (see matrix below). callback opens nothing: the click fires checkoutInitiated and your page owns the action (signup-first funnel; see Defer the buy action). The two item-pinned surfaces are always in-frame and ignore it. |
item | a <planItemId> | (none) | Pricing card only. Deep-links the pricing-card embed straight to one item's in-frame checkout while keeping the cards behind it; Back returns to them. For a checkout with no cards, use the path-segment form …/embed/checkout/<vanity>/<planItemId> instead of this query param. Either id comes from the operator's snippet; it is a server-side id, not a value you construct. |
ctaCallback | 1 | (off) | Pricing card only. Defers custom CTA card clicks to your page: the button fires customCtaClicked and navigates nothing, instead of following its ctaLink. Independent of target. Has no effect on priced cards. |
The buy CTA starts checkout on every surface. The one exception is
target=callback, where your page owns the action.
Change only the query string to switch breakout behaviour or theme — the rest of the snippet is identical:
Code
target × authed checkout-bind — support matrix
The authed checkout-bind works on
every surface, in-frame and breakout. An in-frame checkout binds directly. A
breakout (_blank / _top) binds via a session-locator redirect: the iframe
mints a bound session server-side, then redirects the new tab or host page with
only the opaque session id in the URL fragment. With callback, binding happens
on the resume surface you send the buyer to.
target | Where checkout runs | Bind honored? |
|---|---|---|
inline (+ operator "Allow in-frame checkout" on) | in the iframe | yes (in-frame) |
inline (toggle off) | falls back to a new tab (standalone) | yes (via the session-locator redirect) |
_blank | new tab (standalone) | yes (via the session-locator redirect) |
_top | host page navigates (standalone) | yes (via the session-locator redirect) |
callback | nothing opens — your page owns it | N/A — bind on the resume surface you build |
Binding requires only that your page register a fetchBindToken provider (see
Bind to a customer).
With no provider the buyer goes through the anonymous flow on every mode.
Breakout binding (new tab / redirect)
A breakout binds by creating the bound session server-side first. On a _blank /
_top buy click the iframe:
- Requests a fresh bind ref from your
fetchBindTokenprovider (same on-demand handshake as in-frame). No provider or a null ref produces an anonymous breakout to the plain…/checkout/<…>URL. - Mints a bound session server-side and gets back an opaque
sessionId. - Redirects the breakout to
…/checkout/<planVersionId>/<planItemId>#session=<sessionId>— the session locator in the URL fragment, never the bind ref and never a query param, so it is not sent to any server and not inReferer.
The standalone checkout page reads the #session= fragment, strips it from the URL
immediately, and rehydrates the bound session (its quote plus the read-only Details
pre-fill). The locator is single-use (consumed at confirm), tenant-scoped, and
stripped on arrival. The locator expires after a short TTL; anyone holding the
checkout URL can complete checkout until then.
Send the buyer back to your own page after a breakout checkout completes with
returnUrl (Stripe return_url parity). The server honors it only when its origin
is on the tenant's embeddable-origins allowlist; it has no effect on inline /
callback. Generate the exact (URL-encoded) value at Settings → Portal → Embedding.
Code