Skip to main content
Want to skip the reading and run it live? Check out our demo app or clone the quickstart repo.
This guide shows the full E2E flow - search → cart → checkout → order tracking. You don’t have to use all of it. Jump to whichever feature you need: you can call cart.checkout.purchase directly with a product link, or use products.details standalone without ever touching the cart.

Prerequisites

  • Node.js 18+ (or Bun/Deno)
  • An API key from the Henry Dashboard
  • A server-side environment (API key must stay private)

Setup

1

Install the SDK

npm i @henrylabs/sdk
2

Set your environment variable

#.env.local 
HENRY_API_KEY=your_api_key_here 
3

Initialize the client

import HenrySDK from '@henrylabs/sdk';

const henry = new HenrySDK({
  apiKey: process.env.HENRY_API_KEY!,
});

End-to-end walkthrough

Step 1 - Search for products

Product search is async. The initial call returns a refId so in this example we’ll poll until results arrive.
// Kick off the search
const search = await henry.products.search({
	query: 'Nike Air Max',
	limit: 10,
});

// Poll until complete
async function pollUntilDone<T extends { status: string; refId: string }>(initial: T, poll: (args: { refId: string }) => Promise<T>, intervalMs = 1000): Promise<T> {
	let current = initial;
	while (current.status === 'pending' || current.status === 'processing') {
		await new Promise((r) => setTimeout(r, intervalMs));
		current = await poll({ refId: initial.refId });
	}
	return current;
}

const searchResult = await pollUntilDone(search, (args) => henry.products.pollSearch(args));

const products = searchResult.result?.products ?? [];
const first = products[0];
console.log(first.name, first.price.amount, first.price.currency);
// → "Nike Air Max 270" "150.00" "USD"
Cache the refId - you can re-poll any time to retrieve results without re-running the search.

Step 2 - Fetch product details

Use the product link from the search result to pull full details: rich images, all variants, and live availability.
const detailResult = await henry.products.details({
	link: first.link,
});

const detail = await pollUntilDone(detailResult, (args) => henry.products.pollDetails(args));

const product = detail.result;
console.log(product?.name, product?.variants);

Step 3 - Create a cart

Pass one or more product links to cart.create. The response includes a cartId and a ready-to-use checkoutUrl for hosted checkout.
const cart = await henry.cart.create({
	items: [
		{
			link: first.link,
			quantity: 1,
			variant: { size: '10', color: 'Black' },
		},
	],
});

const { cartId, checkoutUrl } = cart.data;
console.log('Cart:', cartId);
console.log('Checkout URL:', checkoutUrl);
// → https://checkout.henrylabs.ai/cart/<card_id>

Step 4 - Add more items (optional)

You can add products to an existing cart at any time.
await henry.cart.item.add({
	cartId,
	product: {
		link: 'https://www.nike.com/t/air-max-270-shoes/AH8050-002',
		quantity: 2,
	},
});

Step 5 - Launch hosted checkout

The checkoutUrl returned by cart.create points to Henry’s hosted checkout modal. Redirect your user, open it in an iframe, or pop it in a modal - Henry handles payment, address, and tax collection.
// Option A: Full page redirect (server-side)
res.redirect(checkoutUrl);

// Option B: Embed in an iframe (client-side)
const iframe = document.createElement('iframe');
iframe.src = checkoutUrl;
iframe.style.cssText = 'width:100%;height:700px;border:none;border-radius:12px';
document.getElementById('checkout-container')!.appendChild(iframe);

// - [Optional]: Listen for completion events
window.addEventListener('message', (event) => {
	if (!event.origin.endsWith('.henrylabs.ai')) return;

	const { action, orderId } = event.data ?? {};
	if (action === 'orderCompleted') {
		console.log('Order placed:', orderId);
	}
});
Need full control over the checkout UI? See Headless Checkout

Step 6 - Track the order

The recommended approach is webhooks - Henry will POST to your endpoint when the order status changes, so you don’t have to poll. Register a webhook endpoint in the Henry Dashboard, then handle the events:
app.post('/webhooks/henry', express.raw({ type: 'application/json' }), async (req, res) => {
	if (!verifyHenryWebhook(req.body.toString(), req.headers['x-henry-signature'], req.headers['x-henry-timestamp'])) {
		return res.sendStatus(401);
	}

	const { event, data } = JSON.parse(req.body.toString());

	if (event === 'order.purchase.full.complete') {
		console.log('Order complete:', data.refId, data.result?.costs);
	} else if (event === 'order.purchase.full.cancelled') {
		console.log('Order cancelled:', data.refId);
	}

	res.sendStatus(200);
});
See the Webhooks guide for signature verification and the full list of events. If you need to check order status on demand, use orders.list({cartId}).

Next steps

Product Discovery

Deep dive: search filters, async polling, and rich product details

Universal Cart

Create, manage, and configure multi-merchant carts

Checkout

Hosted checkout with postMessage events, and Pro headless checkout

Order Management

List, filter, and poll orders across all your users