Skip to main content

Live Example

Overview

Several Henry operations are async - they return immediately with a refId and a status, and results arrive once processing completes. You poll the corresponding poll* endpoint until status reaches a terminal state. Operations that follow this pattern:
OperationTriggerPoller
Product searchproducts.searchproducts.pollSearch
Product detailsproducts.detailsproducts.pollDetails
Headless checkoutcart.checkout.purchasecart.checkout.pollPurchase
Checkout detailscart.checkout.detailscart.checkout.pollDetails

Statuses

StatusTerminalAction
pendingNoKeep polling
processingNoKeep polling
completeRead result
failedLog error, investigate and retry

Reusable poll helper

This generic helper works for any Henry async operation. Drop it in a shared utility file and import it wherever you need it.
// utils/henry-poll.ts
export async function pollJob<T extends { status: string; refId: string }>(
	initial: T,
	poller: (args: { refId: string }) => Promise<T>,
	options: {
		intervalMs?: number;
		maxAttempts?: number;
	} = {},
): Promise<T> {
	const { intervalMs = 1000, maxAttempts = 60 } = options;

	let result = initial;
	let attempts = 0;

	while ((result.status === 'pending' || result.status === 'processing') && attempts < maxAttempts) {
		await new Promise((r) => setTimeout(r, intervalMs));
		result = await poller({ refId: initial.refId });
		attempts++;
	}

	if (result.status === 'pending' || result.status === 'processing') {
		throw new Error(`Henry request ${initial.refId} timed out after ${maxAttempts} attempts`);
	}

	return result;
}

Examples

import HenrySDK from '@henrylabs/sdk';
import { pollJob } from './utils/henry-poll';

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

const searchResult = await pollJob(
	await henry.products.search({
		query: 'Nike Air Max',
		limit: 20,
		sortBy: 'lowToHigh',
	}),
	(args) => henry.products.pollSearch(args),
);

if (searchResult.status !== 'complete') {
	throw new Error(`Search ${searchResult.status}: ${JSON.stringify(searchResult.error)}`);
}

const { products, pagination } = searchResult.result!;
console.log(`Found ${products.length} products`);

Product details

const detailResult = await pollJob(
	await henry.products.details({
		link: 'https://www.nike.com/t/air-max-270-shoes/AH8050-002',
	}),
	(args) => henry.products.pollDetails(args),
);

const product = detailResult.result;
console.log(product?.name, product?.price);

Headless checkout

Purchase is async too - Henry places the order with the merchant(s) in the background.
const purchaseResult = await pollJob(
	await henry.cart.checkout.purchase({
		cartId: 'your-cart-uuid',
		buyer: {
			name: { firstName: 'Jane', lastName: 'Doe' },
			email: 'jane@example.com',
			shippingAddress: {
				line1: '350 5th Ave',
				city: 'New York',
				province: 'NY',
				postalCode: '10118',
				countryCode: 'US',
			},
			card: {
				details: {
					cardNumber: '4111111111111111',
					expiryMonth: '12',
					expiryYear: '2026',
					cvv: '123',
				},
			},
		},
	}),
	(args) => henry.cart.checkout.pollPurchase(args),
	{ intervalMs: 2000, maxAttempts: 30 },
);

if (purchaseResult.status === 'complete') {
	const { total } = purchaseResult.result!.costs;
	console.log(`Order placed - total: ${total.amount} ${total.currency}`);
}

Tips

The pollJob helper defaults to 1s intervals and 60 attempts, which works for most cases. Product search and details typically complete in 2–5 seconds; orders take longer since Henry places them with the merchant. For order fulfillment, consider skipping polling entirely - Webhooks let Henry push results to you instead.

Next steps

Webhooks

Skip polling for order events - have Henry push results to your server instead

Order Management

List and filter orders after checkout