Overview
Henry’s Headless Checkout gives you complete control over the commerce experience through our Advanced API. Build custom checkout flows while we handle payment infrastructure.
Headless Checkout requires Pro tier access. Contact us
to upgrade.
Perfect for : Teams requiring custom flows and complete control over UI/UX.
Why Choose Headless Checkout?
Complete Control Build exactly the experience your users need
White Label Your brand, your design, no compromises
Advanced Features Card tokenization and custom flows
How It Works
Headless Checkout follows a simple three-step flow:
Use the same x-user-id header across all API calls to maintain user context.
Quick Start
import HenrySDK from "@henrylabs/sdk" ;
const client = new HenrySDK ({
apiKey: process . env . HENRY_API_KEY ! ,
});
Collect Card Details
Use our hosted modal to securely collect payment cards: const cardCollect = await client . wallet . createCardCollection ({
'x-user-id' : 'user_123' ,
auth: false ,
});
// Redirect user to modal
window . location . href = cardCollect . data . modal_url ;
Headless Checkout calls to client.wallet.createCardCollection must
include auth: false.
Search Products
Find products from supported merchants: const { data } = await client . products . search ({
query: 'Nike' ,
});
Add to Cart & Get Quote
Add products to a cart and retrieve a quote (with tax, fees, and a session-token): // 1. Add items to cart
await client . cart . items . add ({
'x-user-id' : 'user_123' ,
productsDetails: [
{
name: "Men's Trail Runners" ,
price: '100' ,
quantity: 1 ,
productLink:
'https://www.on.com/en-us/products/cloud-6-versa-m-3mf1004/mens/black-eclipse-shoes-3MF10040106' ,
productId: 'P01145AC2' ,
metadata: {
color: 'Black' ,
size: '9' ,
},
productImageLink:
'https://images.ctfassets.net/hnk2vsx53n6l/2xi62H2BswFpVK0SjUmhXM/0d4a4bb14915c9a5d3228df45c774629/c36d3fd00cf91ec9fb5ff4bc4d4a0093cccbe8cd.png?w=192&h=192&fm=avif&f=center&fit=fill&q=80' ,
},
],
});
// 2. Get cart quote (session token + totals)
const quote = await client . checkout . session . createQuote ({
'x-user-id' : 'user_123' ,
shippingDetails: {
fullName: 'John Doe' ,
email: '[email protected] ' ,
phoneNumber: '+1234567890' ,
addressLine1: '350 5th Ave' ,
countryCode: 'US' ,
city: 'New York' ,
stateOrProvince: 'New York' ,
postalCode: '10001' ,
},
});
const sessionToken = quote . data . session_token ;
console . log ( sessionToken ); // Use this in checkout
Complete Checkout
Process payment with the stored card: const confirmation = await client . checkout . session . confirmCheckout ({
'x-session-token' : sessionToken ,
'x-user-id' : 'user_123' ,
});
const orderData = confirmation . data ;
console . log ( orderData . id , orderData . status );
Complete Example
Here’s a full implementation:
async function checkout ( userId ) {
// Step 1: Collect card via modal
const cardCollect = await client . wallet . createCardCollection ({
"x-user-id" : userId ,
auth: false ,
});
window . location . href = cardCollect . data . modal_url ;
// Step 2: Search products (after user returns)
const products = await client . products . search ({
query: "Nike" ,
});
const selected = products . data [ 0 ];
// Step 3a: Add to cart
await client . cart . items . add ({
"x-user-id" : userId ,
productsDetails: [
{
name: selected . name ,
price: String ( selected . price ),
quantity: 1 ,
productLink: selected . productLink ,
productId: selected . id ,
productImageLink: selected . imageUrl ,
},
],
});
// Step 3b: Get cart quote (returns session_token)
const quote = await client . checkout . session . createQuote ({
"x-user-id" : userId ,
shippingDetails: {
fullName: "John Doe" ,
email: "[email protected] " ,
phoneNumber: "+1234567890" ,
addressLine1: "350 5th Ave" ,
addressLine2: "Suite 21" ,
countryCode: "US" ,
city: "New York" ,
stateOrProvince: "New York" ,
postalCode: "10001" ,
},
});
const sessionToken = quote . data . session_token ;
// Step 4: Confirm checkout with session_token
const order = await client . checkout . session . confirmCheckout ({
"x-session-token" : sessionToken ,
"x-user-id" : userId ,
});
console . log ( "Order:" , order . data . id );
}
Integration Methods
Method 1: Hosted Iframe
Keep users on your site with an embedded card collection:
async function collectCard ( userId : string ) {
const { data } = await client . wallet . createCardCollection ({
"x-user-id" : userId ,
auth: false ,
});
// Embed in iframe
const iframe = document . createElement ( "iframe" );
iframe . src = data . modal_url ;
iframe . style . width = "100%" ;
iframe . style . height = "600px" ;
iframe . style . border = "none" ;
document . getElementById ( "card-container" )?. appendChild ( iframe );
}
Balance between redirect and embedded:
async function openCardModal ( userId : string ) {
const { data } = await client . wallet . createCardCollection ({
"x-user-id" : userId ,
auth: false ,
});
const modal = window . open (
data . modal_url ,
"henry-card-collection" ,
"width=500,height=700,left=200,top=100" ,
);
window . addEventListener ( "message" , ( event ) => {
if ( event . origin !== "https://checkout.henrylabs.ai" ) return ;
const payload = event . data as { status ?: string } | null ;
if ( payload ?. status === "complete" ) {
modal ?. close ();
handleCardSaved ( payload );
}
});
}
Method 3: Redirect
Simplest integration - redirect users to Henry’s hosted page:
async function redirectToCardCollection ( userId : string ) {
const { data } = await client . wallet . createCardCollection ({
"x-user-id" : userId ,
auth: false ,
});
window . location . href = data . modal_url ;
}
Handling Results
Track order status after checkout:
// Check order status
async function getOrderStatus ( orderId : string ) {
const response = await client . orders . retrieveStatus ( orderId );
return response . data ;
}
// Poll for updates
function pollOrderStatus (
orderId : string ,
callback : ( order : Awaited < ReturnType < typeof getOrderStatus >>) => void ,
) {
const interval = setInterval ( async () => {
const order = await getOrderStatus ( orderId );
callback ( order );
if ([ "delivered" , "cancelled" , "failed" ]. includes ( order . status )) {
clearInterval ( interval );
}
}, 5000 );
return () => clearInterval ( interval );
}
Webhook support for real-time updates is coming soon.
Mobile Integration
Swift
import WebKit
class CardCollectionViewController : UIViewController {
@IBOutlet weak var webView: WKWebView !
func loadCardCollection ( modalUrl : String ) {
if let url = URL ( string : modalUrl) {
webView. load ( URLRequest ( url : url))
}
}
}
Kotlin
class CardCollectionActivity : AppCompatActivity () {
private lateinit var webView: WebView
fun loadCardCollection (modalUrl: String ) {
webView. loadUrl (modalUrl)
webView.settings.javaScriptEnabled = true
}
}
React Native
import { useEffect , useState } from "react" ;
import { WebView } from "react-native-webview" ;
type Props = {
userId : string ;
onComplete ( orderId : string ) : void ;
};
export function CardCollectionScreen ({ userId , onComplete } : Props ) {
const [ modalUrl , setModalUrl ] = useState < string | null >( null );
useEffect (() => {
client . wallet
. createCardCollection ({ "x-user-id" : userId , auth: false })
. then (({ data }) => setModalUrl ( data . modal_url ));
}, [ userId ]);
if ( ! modalUrl ) {
return null ;
}
return (
< WebView
source = {{ uri : modalUrl }}
onMessage = {(event) => {
try {
const payload = JSON . parse ( event . nativeEvent . data ) as {
status ?: string ;
orderId ?: string ;
};
if ( payload . status === "complete" && payload . orderId ) {
onComplete ( payload . orderId );
}
} catch ( error ) {
console . warn ( "Ignoring malformed checkout message" , error );
}
}}
/>
);
}
Next Steps
Ready to implement Headless Checkout?
Test Integration
Use sandbox mode to validate your implementation
Go Live
Switch to production API key when ready
Headless Checkout requires Pro tier access. Start with Hosted
Checkout if you want to launch quickly.