LedgerBee Developer
  • Getting started
  • Conventions
  • Products
  • Configuration
  • API Reference
Subscriptions
Payment flowCard paymentsProducts & PricingProduct Entitlements
Billing documents
Webhooks
Customer Portal
    Portal SSO
    Embedded Checkout
      OverviewPricing cards & snippetLifecycle eventsBind to a customerGated plansFulfillment & errors
Embedded Checkout

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
<iframe src="https://<slug>.portal.ledgerbee.com/embed/<vanity>?lang=en&target=inline&theme=system" data-ledgerbee-pricing loading="lazy" style="width:100%;border:0;min-height:520px" title="Pricing"></iframe> <script src="https://<slug>.portal.ledgerbee.com/embed.js" async></script>

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
<iframe src="https://<slug>.portal.ledgerbee.com/embed/<vanity>?lang=en&theme=system&item=<planItemId>" data-ledgerbee-pricing loading="lazy" style="width:100%;border:0;min-height:520px" title="Checkout"></iframe> <script src="https://<slug>.portal.ledgerbee.com/embed.js" async></script>

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
<iframe src="https://<slug>.portal.ledgerbee.com/embed/checkout/<vanity>/<planItemId>?lang=en&theme=system" data-ledgerbee-pricing loading="lazy" style="width:100%;border:0;min-height:520px" title="Checkout"></iframe> <script src="https://<slug>.portal.ledgerbee.com/embed.js" async></script>

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
<script> window.addEventListener('ledgerbee:embed:customCtaClicked', function (e) { var d = e.detail; // { vanity, planItemId, ctaLink, frame } — no PII // Open your own chat widget / contact modal here, or navigate yourself: // if (d.ctaLink) window.location.href = d.ctaLink; window.openMyChatWidget && window.openMyChatWidget(); }); </script>

The full e.detail payload (and every other embed event) is in Lifecycle events.

Snippet query parameters

ParamValuesDefaultMeaning
langen | daenAll surfaces. Language the cards + checkout render in.
themesystem | light | darksystemAll surfaces. Color theme. system follows the visitor's OS preference.
targetinline | _blank | _top | callbackinlinePricing 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.
itema <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.
ctaCallback1(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
<!-- New tab on buy, dark theme --> <iframe src="https://<slug>.portal.ledgerbee.com/embed/<vanity>?lang=en&target=_blank&theme=dark" data-ledgerbee-pricing loading="lazy" style="width:100%;border:0;min-height:520px" title="Pricing"></iframe> <script src="https://<slug>.portal.ledgerbee.com/embed.js" async></script>

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.

targetWhere checkout runsBind honored?
inline (+ operator "Allow in-frame checkout" on)in the iframeyes (in-frame)
inline (toggle off)falls back to a new tab (standalone)yes (via the session-locator redirect)
_blanknew tab (standalone)yes (via the session-locator redirect)
_tophost page navigates (standalone)yes (via the session-locator redirect)
callbacknothing opens — your page owns itN/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:

  1. Requests a fresh bind ref from your fetchBindToken provider (same on-demand handshake as in-frame). No provider or a null ref produces an anonymous breakout to the plain …/checkout/<…> URL.
  2. Mints a bound session server-side and gets back an opaque sessionId.
  3. 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 in Referer.

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
<iframe src="https://<slug>.portal.ledgerbee.com/embed/<vanity>?lang=en&target=_blank&theme=system&returnUrl=https://your-site.example/thanks" data-ledgerbee-pricing loading="lazy" style="width:100%;border:0;min-height:520px" title="Pricing"></iframe> <script src="https://<slug>.portal.ledgerbee.com/embed.js" async></script>
Last modified on July 30, 2026
OverviewLifecycle events
On this page
  • The snippet
  • What the buyer sees
  • Deep-link to one item
  • Forced single-item checkout
  • Custom call-to-action cards ("talk to sales")
  • Snippet query parameters
    • target × authed checkout-bind — support matrix
    • Breakout binding (new tab / redirect)