SDK Configuration
Reference for EmbeddedCheckoutConfig, EmbeddedCheckoutOptions, configure(), styles, and the SDK error types.
The shape of a call
Everything the SDK needs is split in two: config (who you are, where the API is) and the
options around it (what to pay for, where the frame lives, what to call back).
import { mountEmbeddedCheckout } from '@checkout/sdk'
import '@checkout/sdk/styles.css'
const handle = mountEmbeddedCheckout('#checkout', {
source: { kind: 'checkout', session: 'cs_live_123' },
config: {
apiEndpoint: 'https://api.example.com',
publishableKey: 'pk_live_abcdef0123456789',
},
frameUrl: 'https://pay.example.com/embed',
returnUrl: location.href,
onSuccess: () => console.log('paid'),
})
// later
handle.destroy()EmbeddedCheckoutConfig
The config object. Every field is optional in the type, but apiEndpoint and publishableKey
must come from somewhere — the call site or configure().
Prop
Type
apiEndpoint is required. There is no fallback to the page origin — that would post the
checkout session token to your own server instead of to Lodash. Omitting it throws
MissingEndpointError.
The same six fields are accepted by <Checkout config={…}> in React and by new CheckoutClient(…)
(minus locale and colorScheme, which are UI-only).
EmbeddedCheckoutOptions
The second argument to mountEmbeddedCheckout(), and the prop set of every framework
EmbeddedCheckout component.
Prop
Type
mountEmbeddedCheckout() returns an EmbeddedCheckoutHandle with a single method, destroy(),
which unmounts the frame and removes the 3DS-return message listener. Always call it when the
container goes away.
frameUrl is a base, not a page
frameUrl points at the directory the frame assets are served from. Internally the SDK runs
checkoutFrameUrl(), which appends checkout.html (and is idempotent if you already did).
import { checkoutFrameUrl, resolveFrameUrl } from '@checkout/sdk'
checkoutFrameUrl('https://pay.example.com/embed')
// → 'https://pay.example.com/embed/checkout.html'
checkoutFrameUrl(resolveFrameUrl()) // uses configure({ frameUrl }) when no argument is givencheckout.html is the only frame page (plus checkout-return.html, which the SDK builds for you
via buildCheckoutReturnUrl()).
Publishable key rules
assertPublishableKey() runs before anything is mounted, so a bad key fails loudly at the call site
instead of as a 401 mid-payment. The rule: pk_test_ or pk_live_ followed by at least four
alphanumeric characters, and no longer than 48 characters overall.
| Value | Result |
|---|---|
pk_test_abcdef0123456789 | accepted |
pk_live_ | rejected — prefix only |
pk_test_ab_cd | rejected — underscore in the body |
sk_test_… | rejected — secret key, never put one in a browser |
pkey_… | rejected — an Omise public key |
pk_p256_… | rejected — a service-account encryption key |
a long pk_live_… from another processor | rejected — Stripe shares the prefix; the SDK wants your Lodash key |
You can run the same check yourself:
import { assertPublishableKey, PublishableKeyError } from '@checkout/sdk'
try {
assertPublishableKey(process.env.NEXT_PUBLIC_LODASH_PK, 'boot')
} catch (err) {
if (err instanceof PublishableKeyError) console.error(err.message)
}Redirects and 3DS
Omitting onRedirect is the correct default. The SDK navigates the top window itself via an
internal navigateTop helper that is written to work from inside a cross-origin iframe. If you
pass a handler, you own the navigation — one that only logs will strand the customer mid-3DS on a
blank frame.
// Correct — the SDK performs the redirect
mountEmbeddedCheckout('#checkout', { source, config, frameUrl })
// Only when you must route it yourself
mountEmbeddedCheckout('#checkout', {
source,
config,
frameUrl,
onRedirect: (url) => { window.top.location.href = url },
})Note that window.top.location.assign() throws a SecurityError from a cross-origin iframe — a
cross-origin Location exposes only the href setter. Assign to top.location.href.
configure()
Set the values once at boot instead of repeating them at every call site. configure() is exported
from the root entry and from every framework subpath.
import { configure } from '@checkout/sdk'
configure({
apiEndpoint: 'https://api.example.com',
vaultEndpoint: 'https://vault.example.com',
frameUrl: 'https://pay.example.com/embed',
publishableKey: 'pk_live_abcdef0123456789',
country: 'TH',
})SDKDefaults
Prop
Type
Precedence
A value passed at the call site always wins; configure() only fills the gaps. configure()
replaces the whole defaults object, so call it once with everything.
configure({ apiEndpoint: 'https://api.example.com', publishableKey: 'pk_live_abcdef0123456789' })
mountEmbeddedCheckout('#checkout', {
source: { kind: 'checkout', session: 'cs_live_123' },
config: { apiEndpoint: 'https://api-staging.example.com' }, // wins
frameUrl: 'https://pay.example.com/embed',
})
// → apiEndpoint: https://api-staging.example.com
// → publishableKey: satisfies the parent-side key check from configure()configure() does not cross the frame boundary. The config object you pass at the call site is
what is serialised into the checkout frame. configure() supplies frameUrl and satisfies the
parent-side publishable-key check, but the frame itself never sees those defaults — so keep
apiEndpoint and publishableKey in config for the embedded flow.
You can ask for the resolved value directly — each of these throws MissingEndpointError if
neither source supplied one:
import { resolveApiEndpoint, resolveVaultEndpoint, resolveFrameUrl } from '@checkout/sdk'
resolveApiEndpoint() // from configure()
resolveApiEndpoint('https://api.example.com') // explicit winsColor scheme
config.colorScheme overrides the SDK's CSS custom properties. All fields are optional strings and
accept any CSS colour value.
Prop
Type
<Checkout
source={{ kind: 'checkout', session: 'cs_live_123' }}
config={{
apiEndpoint: 'https://api.example.com',
publishableKey: 'pk_live_abcdef0123456789',
locale: 'th',
colorScheme: { primary: '#6366f1', error: '#dc2626' },
}}
/>To apply the same palette to your own markup, the SDK exports both serialisers:
import { generateCSSVariables } from '@checkout/sdk' // inline style string
import { generateCSSVarsObject } from '@checkout/sdk/react' // React style object
generateCSSVariables({ primary: '#6366f1' })
// → '--checkout-primary: #6366f1'Styles
styles.css is not auto-injected. Import it once per integration or the checkout renders
unstyled.
import '@checkout/sdk/styles.css'Errors
| Error | Thrown when |
|---|---|
MissingEndpointError | No apiEndpoint, vaultEndpoint or frameUrl from either the call site or configure(). The message names the missing key. |
PublishableKeyError | The key is missing, malformed, a bare prefix, a secret key, or another processor's key. |
UnsafeRedirectError | A redirect target that is not http: or https: — an unparseable URL, or a javascript: / data: scheme. |
MissingEndpointError and PublishableKeyError are exported and can be tested with instanceof.
UnsafeRedirectError is raised inside the SDK's redirect path and is not exported — match on
err.name.
import { mountEmbeddedCheckout, MissingEndpointError, PublishableKeyError } from '@checkout/sdk'
try {
mountEmbeddedCheckout('#checkout', { source, config, frameUrl })
} catch (err) {
if (err instanceof MissingEndpointError) reportConfigBug(err.message)
else if (err instanceof PublishableKeyError) reportConfigBug(err.message)
else if (err instanceof Error && err.name === 'UnsafeRedirectError') reportConfigBug(err.message)
else throw err
}All three are configuration mistakes, not runtime payment failures — they should surface in your
build or smoke test, never to a customer. Payment failures arrive through onError instead.