We recommend pinning the
@clink-ai/clink-elements version.What the full chain looks like
Elements only replaces the frontend segment. Creating the Session still has to happen on the merchant server, because that call uses the Secret Key. Step 1 submits a product identifier, not a final amount computed in the browser. The amount is recalculated server-side in step 2.Who owns what
1. Server: write a wrapper Session endpoint
This layer is not an optional optimization. It is three boundaries at once: the Secret Key security boundary, the amount validation boundary, and the local order association boundary. TheclinkRequest helper below is the authenticated helper from Hosted Checkout; it returns the data field of the response.
uiModeiselementsandreturnUrlis required. Put{ELEMENTS_SESSION_ID}in the URL and Clink substitutes the real Session ID- The field is
returnUrl. Older material calls itredirectUrl, which is wrong —redirectUrlis a response field used inrequires_actionflows merchantReferenceIdis for reconciliation only. It is not an idempotency key; the same value twice produces two Sessions
What Clink returns
Thedata that clinkRequest hands back looks like this:
{ "code": 200, "msg": "success", "data": { ... } } envelope; clinkRequest has already unwrapped it.
expireTime currently comes back as "2026-07-30 12:00:00" — no timezone designator, and not RFC 3339.Do not hand it straight to new Date() or parse it as local time; different runtimes will disagree.Do not infer a “merchant account timezone” or the browser timezone either. The backend currently serializes using the service process default timezone, with no per-account conversion, so the string does not state which zone it belongs to.Precise cross-timezone handling has to wait until the backend upgrades this contract to RFC 3339 with an offset, or to a Unix timestamp. Until then, read status to decide whether a Session can still be paid rather than computing from this string.- The response contains no Publishable Key and no
environment. Both come from the merchant application config — see the next section - The response does contain
url, the hosted checkout address. An Elements integration does not use it
2. Frontend: call the wrapper endpoint, then initialize
The browser does two things: ask the merchant backend for asessionId, then initialize the SDK with it.
PUBLIC_CLINK_PUBLISHABLE_KEY and environment are the application deployment config, taken from the Publishable Key under Developers > API Keys (it starts with pk_uat_). They are safe to expose in the browser, but they do not come from the Create Session response.
- Vite
- Next.js
The SDK parameter is named
publishKey. When this page says Publishable Key, that is the field it means — write publishKey in code.3. Mounting, submitting, and third-party buttons
Two kinds of button can appear on a checkout page, and they behave differently: The merchant pay button — for cards and anything else where the host triggers submission. Callclink.submit() on click.
SDK built-in third-party buttons — Apple Pay, Google Pay, PayPal, and similar. The paymentMethod element renders these internally and handles the click itself. When one takes over, the SDK emits submit-visible: false and the host page hides its own button.
submit-enabled reports whether submission is allowed. Write disabled = !enabled rather than passing the event value straight into disabled, which inverts the logic.
Configure SDK button appearance
Starting with0.0.3, pass sdkButtons under presetOptions to configure Apple Pay, Google Pay, Link, and PayPal buttons.
- Keep button height between
40and55pixels for compatibility across the current button renderers - A non-finite or non-positive height falls back to
45; other unsupported positive values can make the affected button fail to initialize instead of falling back - A non-finite or negative radius falls back to
6; values above half the button height are capped at half the height - Unsupported theme and type values fall back to their defaults
- Every SDK button uses
100%width - When Apple Pay has no explicit theme, light mode uses
blackand dark mode useswhite - Clink applies PayPal height and radius to a wrapper around the PayPal component rather than passing them as PayPal SDK options
loadClinkElements() again.
Track SDK button availability
Listen forsdk-button-initialized before mounting paymentMethod so the first event is not missed.
truemeans the button initialized and is available on the current devicefalsemeans the device is unsupported, the configuration is invalid, the gateway does not support it, the third-party SDK failed to load, or the button exceeded the 10-second timeout for its current initialization cycle- Only buttons present in the current Session are included; the payload is
{}when there are no third-party buttons
Track third-party payment dialogs
Listen forsdk-dialog-open and sdk-dialog-close to track when a third-party payment dialog opens or closes. The callback receives the payment method that triggered the event.
applePay, googlePay, link, or paypal.
Configure currency selector styles
Starting with0.0.5, use presetOptions.currencySelect to customize the currency selector. Every style option is a standard CSS style object, written the same way as React’s style prop.
With multiple currencies, each option styles the following area (
[US] and [EU] stand for flag icons):
oneCurrencyStylereplaces the default style entirely, so set the font size, weight, and color yourself- Options under
switcheronly override the properties you set; everything else keeps its default look - The selected card applies
selectedItemStyleandselectedTextStyleon top ofitemStyleandtextStyle; selected values win on conflicts - Setting a border or background color in
itemStyle, or a text color or weight intextStyle, overrides the default selected highlight — set the matching selected style as well - When
hideIfOneCurrencyistrueand only one currency is available,oneCurrencyStylehas no effect - The heading, flag icons, and exchange-rate note cannot be customized yet
selectedItemStyle; everything else comes from itemStyle:
loadClinkElements() again.
Use Express Checkout
Starting with0.1.0, paymentMethod supports an Express Checkout mode. It shows only the Apple Pay, Google Pay, PayPal, and Link express buttons.
Express Checkout is not a new element type. Turn it on by passing a second argument when creating paymentMethod:
paymentMethod stays in standard mode and behaves as before.
How the two modes differ
Shared options in
presetOptions — locale, theme, primaryColor, radius, and so on — still apply in Express Checkout. You can also create and mount currencySelect as usual.
Options
The full type is:- Height and radius are set once for all buttons; per-button values are not supported
- Button labels come from the third-party SDK through
buttonType - Buttons stack vertically with an
8pxgap and fill the container width
Button order and visibility
WhenpaymentMethodOrder is set:
- Buttons follow the configured order (case-insensitive; duplicates and unsupported values are ignored)
- Methods the Session returns but the list omits are appended in their original Session order
paypal, applePay, googlePay and the option is ['googlePay'], the final order is googlePay, paypal, applePay.
A button shows only when all three hold:
- The Session returns that payment method (depends on merchant configuration, currency, and so on)
- Its
visibleis not'never' - The current browser and device support it, and the third-party SDK initializes
sdk-button-initialized to learn which buttons are actually available.
When the Session has no express payment methods at all, the element renders nothing — not even the address fields — and logs a console warning.
Billing address and tax
When the Session needs tax, the element shows the required address fields — country, address, city, postal code, and so on — above the buttons. When the customer changes the address, Clink recalculates tax and updates the amount; the buttons are temporarily disabled meanwhile. The new amount is also sent to the host page throughamount-change. Use the amount from that event in your order summary.
Events
Express Checkout reuses the existing events; there is nothing new to listen for:submit-enabled and submit-visible exist for the host pay button and need no handling in Express Checkout.
Limits
modeis fixed at creation and cannot be switched later. To change modes, destroy the instance and callloadClinkElements()again- One instance can create only one
paymentMethod, so a single instance cannot provide both the standard payment form and Express buttons - As in standard mode,
session-successis not proof of payment; the final status comes from the webhook your backend receives
4. Events
Two groups, by what they are used for. Update host UI
Flow signals
5. Promo codes
Optional. Creating Coupons and Promotion Codes server-side is covered in Discounts and promotion codes. This section is frontend only.{ amount }, with the fields nested under amount. Writing (info) => info.enablePromotionCode yields undefined, and the promo code entry never appears.
Discounts and the final amount due come from amount. Do not recompute them.
6. Error handling
0.0.5 throws three different kinds of thing, and they are handled differently.
Initialization: loadClinkElements()
It does not throw only ClinkApiError. There are at least three paths:
A response body that is not valid JSON also surfaces as a native
SyntaxError from await res.json().
So treat the catch parameter as unknown and always keep a fallback branch:
After initialization: some errors are thrown synchronously
Not every runtime problem arrives as an event. These are synchronously thrown nativeErrors and need a try/catch:
Errors during the payment flow: events
Errors that belong to the payment flow itself are forwarded from the checkout iframe as events, and are not wrapped in any error class.7. Implementation constraints
Get these wrong and the integration breaks. Everything else about layout is a free design decision.Before shipping
- No Secret Key and no webhook signing key in browser Network traffic or the build output
- The browser only calls the merchant Session wrapper endpoint, never Clink’s Create Session directly
- The backend does not trust a final amount from the browser; it recalculates from the product or cart
- The local order,
merchantReferenceId, andsessionIdmapping is stored - The Publishable Key and
environmentcome from application config, and are not described as Create Session response fields -
submit-visiblehides the host button when Apple Pay, Google Pay, PayPal, or similar appear - With Express Checkout, the page has no host pay button and handles the case where no button is available
-
session-successdoes not trigger fulfillment; the final state comes from verified webhooks and backend queries - Old instances are destroyed when the Session changes or the page unmounts
Next
Hosted Checkout
The backend and webhook halves are identical to Hosted Checkout.
Go live
The checklist before switching to production.