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 Tools Library determines whether a user can complete checkout directly on the merchant site using an embedded wallet experience.
When embedded checkout is available, the user remains on the merchant domain and authenticates using passkeys (cross-domain) through an embedded wallet iframe.
When embedded checkout is not available, the integration automatically falls back to a redirect-based checkout flow hosted on the wallet domain.


Overview Flow
Architecture Overview
The integration consists of three components:
Merchant Domain
Hosts the checkout experience and controls the checkout flow.
Responsibilities:
- Launch discovery
- Generate or retrieve a
merchantTrustId - Launch embedded checkout
- Handle fallback redirects
- Persist embedded checkout status
Discovery Page
A lightweight page hosted on the wallet domain.
The discovery page determines whether embedded checkout is supported in the current browser and device environment.
Because passkey availability must be evaluated from the wallet origin, discovery must run on the wallet domain.
Wallet Checkout
Handles authentication, signing, and transaction approval.
Depending on the discovery result, checkout is displayed:
- Inside an embedded iframe
- As a full-page wallet redirect
Integration
Step 1: Run Discovery
Load a hidden iframe that points to the wallet-hosted discovery page.
The discovery page:
- Calls Wallet SDK discover method
- Evaluates passkey availability
- Returns a
DiscoveryResultusingpostMessage
Possible values for the flow field are:
| Result | Description |
|---|---|
EMBED | Embedded checkout is supported |
REDIRECT | Embedded checkout is not recommended |
Merchants can use the LoginID Merchant Tools library to perform discovery instead of
manually creating a hidden iframe and handling postMessage communication.
import { LoginIDMerchantCheckout } from "@loginid/checkout-merchant";
const result = await LoginIDMerchantCheckout.discover("https://wallet/discover");
if (result.flow === "EMBED") {
// Launch embedded checkout
}
Step 2: Generate or Retrieve a merchantTrustId
Before starting checkout, the merchant must provide a merchantTrustId.
The merchantTrustId is an identifier that links a wallet account.
What is merchantTrustId?
A merchantTrustId is a cryptographic identifier that links a wallet account to a browser that has successfully completed passkey registration.
It serves two purposes:
- Account Association — it allows the wallet to determine which wallet account the checkout belongs to.
- Possession — it proves that the browser participating in the checkout is the same browser that previously registered the passkey.
The merchantTrustId is passed from the merchant domain to the wallet domain during checkout. It is cryptographically signed and bound to the browser where passkey registration occurred, allowing the wallet to verify both account association and possession of the browser that originally registered the passkey.
merchantTrustId was previously called checkoutId. These terms refer to the same identifier. Older documentation, code samples, or integrations may still use the name checkoutId.
How is a merchantTrustId Generated?
- The merchant generates a browser-bound key pair using the Web Crypto API.
- The private key is stored as a non-exportable key in IndexedDB.
- The browser signs a checkout payload using the stored private key.
- The JWS header contains a fingerprint or identifier for the associated public key.
- The resulting JWS becomes the
merchantTrustId. - The wallet validates the signature and uses the public key fingerprint to resolve the associated account.
The LoginID Merchant Tools library can generate, store, and reuse the browser-bound key material and automatically create a valid merchantTrustId.
import { LoginIDMerchantCheckout } from "@loginid/checkout-merchant";
// Generates a new merchantTrustId if one does not exist,
// otherwise signs and returns the existing merchantTrustId.
const merchantTrustId = await LoginIDMerchantCheckout.getMerchantTrustId();
This approach ensures that only the browser that originally completed passkey registration can generate valid merchantTrustId signatures.
Step 3: Start Checkout
After discovery completes:
Embedded Path
If:
result.flow === "EMBED"
then:
- Create checkout iframe
- Load wallet checkout page
- Send
merchantTrustIdviapostMessage - Provide
merchantTrustIdto the Wallet SDK beginFlow method - Complete authentication and signing inside iframe
Redirect Path
If embedded checkout is unavailable:
- Redirect user to wallet checkout
- Provide
merchantTrustIdto the Wallet SDK beginFlow method - Complete authentication on wallet domain
Optimizing Returning Users
After a successful embedded checkout, persist a flag on the merchant domain:
localStorage.setItem("lid-checkout", "true");
This indicates that the user previously completed an embedded checkout on this browser and likely has passkey support available.
Future Visits
If the flag exists:
- Skip discovery
- Launch embedded checkout immediately
If the flag does not exist:
- Run discovery
- Result determines the best checkout experience
This optimization reduces page load time and avoids unnecessary discovery requests for returning users.
Sequence Diagram
Example Implementations
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:
merchantTrustId- iframe messaging
- 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 {
LoginIDMerchantCheckout,
ParentMessages,
} from "@loginid/checkout-merchant";
const DISCOVERY_URL = "https://wallet/discover";
const WALLET_URL = "https://wallet";
const EMBEDDED_CHECKOUT_FLAG = "lid-checkout";
const body = document.body;
// Optional optimization for returning users
const hasEmbeddedCheckout = localStorage.getItem(EMBEDDED_CHECKOUT_FLAG) === "true";
// Step 1: Run Discovery
const discoveryResult = hasEmbeddedCheckout
? { flow: "EMBED" }
: await LoginIDMerchantCheckout.discover(DISCOVERY_URL);
// Step 2: Generate or Retrieve merchantTrustId
const merchantTrustId = await LoginIDMerchantCheckout.getMerchantTrustId();
// Step 3: Start Checkout
if (discoveryResult.flow === "EMBED") {
const checkoutIframe = document.createElement("iframe");
checkoutIframe.src = WALLET_URL;
checkoutIframe.width = "90%";
checkoutIframe.height = "90%";
checkoutIframe.allow =
"publickey-credentials-get *; " +
"publickey-credentials-create *; bluetooth";
body.appendChild(checkoutIframe);
const walletMessages = new ParentMessages(checkoutIframe);
const result = await walletMessages.sendMessage(
"sign_transaction",
{ merchantTrustId }
);
if (result.error) {
throw new Error(result.error);
}
localStorage.setItem(EMBEDDED_CHECKOUT_FLAG, "true");
checkoutIframe.remove();
} else {
window.location.href = `${WALLET_URL}/checkout?merchantTrustId=${merchantTrustId}`;
}
Here is a sample from our checkout demo that uses a custom approach for communicating with the iframe and
handling the transfer of the merchantTrustId.
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.