> ## Documentation Index
> Fetch the complete documentation index at: https://docs.henrylabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Polling

> Handle async operations with the polling pattern - product search, product details, and headless checkout

## 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:

| Operation         | Trigger                  | Poller                       |
| ----------------- | ------------------------ | ---------------------------- |
| Product search    | `products.search`        | `products.pollSearch`        |
| Product details   | `products.details`       | `products.pollDetails`       |
| Headless checkout | `cart.checkout.purchase` | `cart.checkout.pollPurchase` |
| Checkout details  | `cart.checkout.details`  | `cart.checkout.pollDetails`  |

***

## Statuses

```mermaid theme={null}
stateDiagram-v2
    direction LR
    [*] --> pending : Request received
    pending --> processing : Processing started
    processing --> complete : Results ready
    processing --> failed : Unrecoverable error
    complete --> [*]
    failed --> [*]
```

| Status       | Terminal | Action                             |
| ------------ | -------- | ---------------------------------- |
| `pending`    | No       | Keep polling                       |
| `processing` | No       | Keep polling                       |
| `complete`   | ✅        | Read `result`                      |
| `failed`     | ✅        | Log `error`, investigate and retry |

***

## Sync mode - skip polling entirely

All async operations support `mode: "sync"`, which blocks the request for up to 30 seconds until the operation completes. If the result is ready within that window, you get it in a single call with no polling needed.

```typescript theme={null}
const search = await henry.products.search({
	type: 'global',
	filters: { type: 'text', query: 'Nike Air Max' },
	mode: 'sync',
});

// If completed within 30s, result is already populated
if (search.status === 'complete') {
	console.log(search.result?.products);
}
```

<Info>If the operation doesn't complete within 30 seconds, sync mode returns with a non-terminal status (`pending` or `processing`) and you'll need to fall back to polling. For operations that reliably take longer (e.g. headless checkout), prefer `mode: "async"` with the poll helper below.</Info>

***

## 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.

```typescript theme={null}
// 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

### Product search

```typescript theme={null}
import HenrySDK from '@henrylabs/sdk';
import { pollJob } from './utils/henry-poll';

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

const searchResult = await pollJob(
	await henry.products.search({
		type: 'global',
		filters: {
			type: 'text',
			query: 'Nike Air Max',
			sortBy: 'price-low-to-high',
		},
		limit: 20,
	}),
	(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

```typescript theme={null}
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.

```typescript theme={null}
const purchaseResult = await pollJob(
	await henry.cart.checkout.purchase('crt_sa2aEsCz9PRM', {
		buyer: {
			name: { firstName: 'Jane', lastName: 'Doe' },
			email: 'jane@example.com',
			phone: '+19175551234',
			shippingAddress: {
				line1: '350 5th Ave',
				city: 'New York',
				province: 'NY',
				postalCode: '10118',
				countryCode: 'US',
			},
			card: {
				nameOnCard: { firstName: 'Jane', lastName: 'Doe' },
				details: {
					cardToken: 'card_live_...',
				},
			},
		},
	}),
	(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.value} ${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](/v1/sdk/server/guides/webhooks) let Henry push results to you instead.

***

## Next steps

<CardGroup cols={2}>
  <Card title="Webhooks" icon="bolt" href="/v1/sdk/server/guides/webhooks">
    Skip polling for order events - have Henry push results to your server instead
  </Card>

  <Card title="Order Management" icon="receipt" href="/v1/sdk/server/guides/order-management">
    List and filter orders after checkout
  </Card>
</CardGroup>
