Merchant Setup
A merchant is any business or platform integrating checkout into their website — for example, an online store selling clothing or a booking site for events.
The Merchant SDK provides the tools that determine the checkout flow. If conditions are met, an embedded iframe delivers a seamless, on-site experience with cross-domain passkey authentication and integrated UI. If not, it will gracefully falls back to a custom login, such as a full-page redirect to a bank login.


Integration
You’ll need to implement:
- A custom
<iframe>
setup postMessage
communication between merchant and wallet domains- A fallback for cases where embedding isn’t recommended
Basic Flow
-
Start with a discovery page
Use the Wallet SDK's
discover
method to determine if passkey login is available. -
Generate a
checkoutId
Create or retrieve a
checkoutId
, and pass it to the Wallet in the iframe URL or viapostMessage
. -
Track embedded completion
After a successful embedded checkout, set a persistent client-side flag (e.g., in localStorage) on the merchant domain.
What is checkoutId
?
A checkoutId
is a signed token (JWS) that represents a checkout session.
It connects the user, the current transaction, and the Wallet SDK interactions across both embedded and fallback
flows — without exposing personal information like usernames or emails.
The token is signed using a key pair generated via the Web Crypto API and stored in IndexedDB due to its unexportable capabilities.
This ensures that only the merchant can issue valid checkoutId
s.
Key characteristics:
- The
checkoutId
is created/obtained and signed by the merchant. - It contains no personal data.
- The JWS header includes a
jwk
claim referencing the public key. - It must be passed to the Wallet domain (via iframe
postMessage
or redirect) to initiate the authentication and signing process.
Discovery Page
The discovery page must be hosted on the wallet domain. It’s responsible for determining whether an embedded checkout is possible.
To implement it:
- Create a hidden iframe that points to the discovery page.
- On load, call the Wallet SDK’s discover method.
- Use
postMessage
to return the result to the merchant domain.
Based on the result, decide how to proceed:
- If
DiscoveryResult.flow
isEMBED
, continue with the embedded checkout. - Otherwise, trigger your fallback flow (e.g. redirect to wallet).
Pass CheckoutId
to Wallet
Before starting the embedded or fallback checkout flow, you must pass a checkoutId
to the wallet domain.
- If a
checkoutId
already exists locally, reuse it. - If not, generate a new one and send it to the wallet.
This identifier is a key part of the checkout session and helps associate the user with the transaction.
Flagging Completed Embedded Flow
Once a user completes a checkout session via the embedded flow, you should persist a flag on the merchant
domain (e.g., using localStorage
).
This flag signals that the user has previously completed an embedded checkout on this browser and likely has a passkey available.
Use this flag to optimize future checkouts:
-
If the flag exists:
Skip discovery and proceed directly with the embedded checkout flow.
-
If the flag is missing:
- Run the Wallet SDK’s discovery flow via the discovery page.
- If the result is
EMBEDDED_CONTEXT
, proceed with embedded checkout. - Otherwise, fall back to your default checkout method.
Full Example
Here are somes examples of how your setup might look on the merchant side:
There are two ways to integrate:
- With LoginID Merchant Library – Recommended for most merchants. Offers built-in utilities for managing
checkoutId
, iframe messaging, and discovery. - Without LoginID Merchant Library – Use this if you need full control or want to integrate directly without relying on the library.
Select the tab below based on your preferred approach.
- With LoginID Merchant Library
- Without LoginID Merchant Library
Setup:
- Javascript
Install with command:
npm i @loginid/checkout-merchant
Example:
import { CheckoutIdStore, ParentMessages } from "@loginid/checkout-merchant";
const walletDiscoveryUrl = "https://wallet/discover";
// Step 1: Create a hidden iframe pointing to the discovery page hosted on the wallet domain.
// This allows the merchant to determine if embedded checkout is supported.
const hiddenIframe = document.createElement("iframe");
hiddenIframe.width = "0";
hiddenIframe.height = "0";
hiddenIframe.style.display = "none";
hiddenIframe.src = walletDiscoveryUrl;
// Step 2: Check if a user has already completed an embedded checkout.
// If true, we can skip discovery and go straight to embedding.
const hasBeenEmbedded = localStorage.getItem("lid-checkout");
const body = document.querySelector("body")
// Step 3: Inject hidden discovery iframe into the page.
body?.appendChild(hiddenIframe);
const store = new CheckoutIdStore();
// Step 4: Generate or retrieve a persistent checkoutId.
const checkoutId = await store.setOrSignWithCheckoutId();
const messages = new ParentMessages(hiddenIframe);
// Step 5: If the user has previously completed embedded checkout, skip discovery.
// Otherwise, run discovery via postMessage to determine if embedded flow is possible.
const result = hasBeenEmbedded
? { flow: "EMBED" }
: await messages.sendMessage("discover") as any;
if (result.flow === "EMBED") {
const walletUrl = "https://wallet";
// Step 6: Proceed with embedded checkout by injecting a new iframe to load the wallet.
const iframe = document.createElement("iframe");
iframe.width = "90%";
iframe.height = "90%";
iframe.style.display = "block";
iframe.allow =
"publickey-credentials-get *; publickey-credentials-create *; bluetooth";
iframe.src = walletUrl;
// Remove the discovery iframe since it's no longer needed.
hiddenIframe.remove();
body?.appendChild(iframe);
const messages = new ParentMessages(iframe);
// Step 7: Pass the checkoutId to the wallet to initiate the signing process.
const result = await messages.sendMessage("sign_transaction", { checkoutId }) as any;
if (result.error) {
// Step 8: Handle signing failure by removing iframe and throwing an error.
iframe.remove();
throw new Error(result.error);
}
// Step 9: On success, set a flag in localStorage to remember that the user has used embedded checkout.
localStorage.setItem("lid-checkout", "true");
// Optional: Remove iframe after successful checkout flow.
iframe.remove();
return
} else {
// Step 10: Handle fallback scenario if embedded checkout is not available.
// This might include redirecting the user to a full-page wallet login.
}
Here is a sample from our checkout demo that uses a custom approach for communicating with the iframe and
handling the transfer of the checkoutId
.
This approach doesn't rely on the Merchant library directly.
See the implementation details here.
Need help integrating secure embedded checkout on your merchant domain? Reach out to our support team at support@loginid.io for personalized assistance.
Having issues? Visit our troubleshooting section for common questions and solutions.