# Pricing cards & snippet

## 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.

## 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](/guides/embedded-checkout/customer-binding#defer-the-buy-action-to-your-page-signup-first-funnel)). 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.

### `target` × authed checkout-bind — support matrix

The [authed checkout-bind](/guides/embedded-checkout/customer-binding) 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](/guides/embedded-checkout/customer-binding#2-register-a-fetchbindtoken-provider-on-your-page)).
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.
