Skip to main content

Overview

Henry’s Hosted Checkout is the fastest way to add commerce capabilities to your application. With our hosted checkout solution, you can accept payments without handling sensitive payment data or building complex UI components. Simply embed our checkout experience via iframe, webview, or redirect.
Perfect for: Startups and businesses that want to move fast and launch quickly.

Why Choose Hosted Checkout?

Launch in Minutes

Pre-built UI components and hosted pages get you live fast

Zero PCI Burden

We handle all payment security and compliance requirements

Mobile Ready

Responsive design works seamlessly across all devices

How It Works

Hosted Checkout uses a simple redirect or embedded iframe approach:

Quick Start

Get up and running in four simple steps:
import HenrySDK from "@henrylabs/sdk";

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

Search Products

Find products from supported merchants:
const { data: products } = await client.products.search({
  query: 'running shoes',
});

// Display products to user
2

Initialize Cart

Add products to the cart using our simple API:
await client.cart.items.add({
  'x-user-id': 'user_123',
  productsDetails: [
    {
      productId: 'product_123',
      name: 'Premium Sneakers',
      price: '129.99',
      quantity: 1,
      productLink: 'https://store.com/sneakers',
      productImageLink: 'https://image.url',
      metadata: {
        Size: '10',
        Color: 'Black',
      },
    },
  ],
});
3

Create Checkout Session

Initiate checkout to get the hosted checkout URL:
const checkout = await client.cart.createCheckout({
  'x-user-id': 'user_123',
});

const checkoutUrl = checkout.data.checkout_url;
4

Redirect to Checkout

Send your user to the hosted checkout page:
// Option 1: Full page redirect
window.location.href = checkoutUrl;

// Option 2: Hosted iframe
const iframe = document.createElement('iframe');
iframe.src = checkoutUrl;
iframe.style.width = '100%';
iframe.style.height = '600px';
document.getElementById('checkout-container')?.appendChild(iframe);

// Option 3: Modal popup
window.open(checkoutUrl, 'checkout', 'width=600,height=800');

Preview Checkout Totals (Optional)

Use the session APIs to preview taxes and shipping before opening the hosted experience. Collect the buyer’s shipping details in your app, generate a quote, and read the normalized address if you want to display it back to the shopper.
const quote = await client.checkout.session.createQuote({
  "x-user-id": "user_123",
  shippingDetails: {
    fullName: "Jamie Merchant",
    email: "[email protected]",
    phoneNumber: "+19175551234",
    addressLine1: "350 5th Ave",
    city: "New York",
    stateOrProvince: "NY",
    postalCode: "10118",
    countryCode: "US",
  },
});

const sessionToken = quote.data.session_token;
const orderMetadata = quote.data.order_metadata;

// Optional: show Henry-calculated totals before redirecting the buyer
console.log(orderMetadata.total_price, orderMetadata.tax);

const shipping = await client.checkout.session.retrieveShippingInfo({
  "x-session-token": sessionToken,
});

const { shippingDetails, hasShipping } = shipping.data;

if (hasShipping) {
  console.log("Validated shipping:", shippingDetails);
}

Integration Methods

Method 1: Redirect

The simplest integration - redirect users to Henry’s hosted checkout:
async function handleCheckout() {
  const checkout = await client.cart.createCheckout({
    "x-user-id": "user_123",
  });

  window.location.href = checkout.data.checkout_url;
}

Method 2: Hosted Iframe

Keep users on your site with an embedded checkout:
<div id="checkout-container">
  <iframe
    id="henry-checkout"
    src="{checkoutUrl}"
    width="100%"
    height="600"
    frameborder="0"
    allow="payment"
  >
  </iframe>
</div>
// Listen for checkout events
window.addEventListener("message", (event) => {
  // Verify the origin for security
  if (
    event.origin !== "https://checkout.henrylabs.ai" &&
    event.origin !== "https://checkout.sandbox.henrylabs.ai"
  ) {
    return;
  }

  const payload = event.data as {
    action?: string;
    orderId?: string;
    status?: string;
  } | null;

  if (!payload?.action) return;

  const { status, action, orderId } = payload;

  // Handle checkout events
  switch (action) {
    case "orderCompleted":
      console.log(`Order ${orderId} completed with status: ${status}`);
      handleOrderSuccess(orderId);
      break;

    case "checkoutClosed":
      console.log(`Checkout closed with status: ${status}`);
      handleCheckoutClosed();
      break;
  }
});

Method 3: Modal/Popup

Balance between redirect and embedded:
async function openCheckoutModal() {
  const checkout = await client.cart.createCheckout({
    "x-user-id": "user_123",
  });

  const checkoutUrl = checkout.data.checkout_url;
  const modal = window.open(
    checkoutUrl,
    "henry-checkout",
    "width=600,height=800,left=200,top=100",
  );

  // Listen for checkout events
  window.addEventListener("message", (event) => {
    if (
      event.origin !== "https://checkout.henrylabs.ai" &&
      event.origin !== "https://checkout.sandbox.henrylabs.ai"
    ) {
      return;
    }

    const payload = event.data as { action?: string; orderId?: string } | null;
    if (!payload?.action) return;

    if (payload.action === "orderCompleted") {
      modal?.close();
      handleOrderSuccess(payload.orderId);
    } else if (payload.action === "checkoutClosed") {
      modal?.close();
      handleCheckoutCancelled();
    }
  });
}

Event Handling

When using iframe or popup integration, Henry sends postMessage events to communicate checkout status:

Available Events

Implementation Example

// Track all checkout events
const eventLog: Array<{
  action: string;
  orderId?: string;
  status: string;
  timestamp: string;
}> = [];

window.addEventListener("message", (event) => {
  // Security: Verify the origin
  if (
    event.origin !== "https://checkout.henrylabs.ai" &&
    event.origin !== "https://checkout.sandbox.henrylabs.ai"
  )
    return;

  if (event.data) {
    const { status, action, orderId } = event.data;

    // Log all events from iframe
    if (action === "orderCompleted" || action === "checkoutClosed") {
      const newEvent = {
        action,
        orderId,
        status,
        timestamp: new Date().toLocaleTimeString(),
      };
      eventLog.push(newEvent);

      console.log(
        "ACTION:",
        JSON.stringify({
          action,
          orderId,
          status,
        }),
      );

      // Handle specific events
      if (action === "orderCompleted") {
        // Order successful - show confirmation
        showOrderConfirmation(orderId);
        // Optionally close/hide the iframe
        document.getElementById("henry-checkout")!.style.display = "none";
      } else if (action === "checkoutClosed") {
        // User closed checkout - clean up UI
        hideCheckoutModal();
      }
    }
  }
});
Always verify the event origin to prevent security vulnerabilities. Only accept messages from Henry’s checkout domains: - https://checkout.henrylabs.ai (production) - https://checkout.sandbox.henrylabs.ai (sandbox)

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 CheckoutViewController: UIViewController {
    @IBOutlet weak var webView: WKWebView!

    func loadCheckout(url: String) {
        if let checkoutURL = URL(string: url) {
            webView.load(URLRequest(url: checkoutURL))
        }
    }
}

Kotlin

class CheckoutActivity : AppCompatActivity() {
    private lateinit var webView: WebView

    fun loadCheckout(url: String) {
        webView.loadUrl(url)
        webView.settings.javaScriptEnabled = true
    }
}

React Native

import { WebView } from "react-native-webview";

function CheckoutScreen({ checkoutUrl }) {
  return (
    <WebView
      source={{ uri: checkoutUrl }}
      onMessage={(event) => {
        const data = JSON.parse(event.nativeEvent.data);
        if (data.status === "complete") {
          navigation.navigate("Success");
        }
      }}
    />
  );
}

Next Steps

Ready to implement Hosted Checkout?
1

Get API Key

Onboard onto Henry to get started
2

Review Cart API

Explore the Cart endpoints for managing items
3

Test Integration

Use test mode to validate your implementation
4

Go Live

Switch to production API key when ready
Hosted Checkout is the recommended starting point for most businesses. You can always upgrade to Headless Checkout as your needs grow (requires Pro tier).