---
title: Lead capture on Hydrogen storefronts
description: >-
  Run Shop SDK lead capture on a Hydrogen storefront by routing sign-in through
  your online store.
source_url:
  html: 'https://shopify.dev/docs/api/shop/guides/use-cases/hydrogen-storefronts'
  md: 'https://shopify.dev/docs/api/shop/guides/use-cases/hydrogen-storefronts.md'
---

# Lead capture on Hydrogen storefronts

Lead capture signs buyers in through routes that your online store serves. A Hydrogen storefront doesn't serve those routes, so on a Hydrogen page you tell the Shop SDK where your online store is with the `storefrontOrigin` attribute. The SDK then runs sign-in through the online store and brings the buyer back to your storefront.

***

## What you'll learn

In this guide, you'll learn how to do the following tasks:

* Point lead capture at your online store with `storefrontOrigin`.
* Allow your online store and Shop in your storefront's Content Security Policy.
* Apply the buyer's discount to your Hydrogen cart.
* Handle the errors that lead capture reports on Hydrogen storefronts.

***

## Requirements

* A working [lead capture](https://shopify.dev/docs/api/shop/guides/use-cases/lead-capture) integration that uses a button. Set `buttonType` to anything other than `'none'`, which is the default. On Hydrogen storefronts, lead capture signs buyers in through a sign-in window on desktop and a redirect on mobile. It doesn't support the embedded flow that `'none'` uses.
* A Hydrogen storefront that's deployed to Oxygen and served on a custom domain that's connected to it in the Hydrogen channel, such as `example.com`. Shopify trusts that domain for sign-in, so you don't need to register it. Lead capture doesn't support other headless storefronts.
* An online store domain on the same site as your storefront, such as `checkout.example.com` for a storefront at `example.com`. The buyer's online store session cookie is sent only between same-site origins, so a `*.myshopify.com` domain doesn't work. For the same reason, test on your custom domain, not on an Oxygen preview URL.

***

## Step 1: Set `storefrontOrigin` and `returnTo`

Pass your online store's origin as `storefrontOrigin` when you create the lead capture instance. It must be an `https` origin with no path, query, or hash. This is usually your checkout domain, the `PUBLIC_CHECKOUT_DOMAIN` value in your Hydrogen environment variables.

If you build an app that adds lead capture to many stores, then you need each store's online store origin, because the SDK can't detect it.

Also set `returnTo` to `'same-page'`. On mobile, sign-in redirects the buyer to your online store and back. The default, `'index'`, brings the buyer back to your storefront's home page. With `'same-page'`, they return to the page where they started, so lead capture can finish sign-in there and call your handlers:

## JavaScript

```javascript
const leadCapture = await sdk.create('lead-capture', {
  attributes: {
    buttonType: 'save',
    returnTo: 'same-page',
    storefrontOrigin: 'https://checkout.example.com',
  },
  onComplete(event) {
    console.log('Lead captured:', event.email);
  },
  onError(event) {
    console.error('Lead capture error:', event.code, event.message);
  },
});


document.querySelector('#shop-container').appendChild(leadCapture.element);
```

With `storefrontOrigin` set, the SDK loads Shop's sign-in from your online store and sends its requests there with the buyer's session.

If the value isn't a valid origin, then the SDK ignores it, logs a console warning that explains why, and uses your page's origin instead. Lead capture then reports the `storefront_origin_required` error.

***

## Step 2: Update your Content Security Policy

If your storefront sets a Content Security Policy, then allow your online store and Shop:

| Directive | Add |
| - | - |
| `frame-src` | Your `storefrontOrigin` (for example, `https://checkout.example.com`) and `https://shop.app` |
| `connect-src` | Your `storefrontOrigin`, `https://monorail-edge.shopifysvc.com`, and `https://otlp-http-production.shopifysvc.com` |
| `script-src` | `https://cdn.shopify.com` |

Without `frame-src`, Shop can't recognize returning buyers. Without `connect-src`, the SDK can't finish sign-in, and Shopify can't see errors from your storefront.

If you build the policy with Hydrogen's `createContentSecurityPolicy`, then its `shop.checkoutDomain` option already adds your checkout domain to `connect-src`, and its defaults allow Monorail and `https://cdn.shopify.com`. Its default policy doesn't allow frames from your online store or Shop, so add them to `frameSrc`, and add the OpenTelemetry host to `connectSrc`:

## JavaScript

```javascript
const checkoutDomain = env.PUBLIC_CHECKOUT_DOMAIN;


const {nonce, header, NonceProvider} = createContentSecurityPolicy({
  shop: {
    checkoutDomain,
    storeDomain: env.PUBLIC_STORE_DOMAIN,
  },
  frameSrc: ["'self'", `https://${checkoutDomain}`, 'https://shop.app'],
  connectSrc: ['https://otlp-http-production.shopifysvc.com'],
});
```

Setting `frameSrc` replaces the default policy for frames, so include any other frame sources that your storefront already uses.

***

## Step 3: Apply the discount to your cart

On an online store page, the SDK applies the discount you return from `onAuthenticate` to the buyer's online store cart. A Hydrogen storefront has its own cart, so the SDK skips that step. The SDK still saves the discount to the buyer's Shop account. If saving it fails, then lead capture reports a `no_discount_received` error and still completes sign-in, because you apply the code yourself.

Apply the code to your Hydrogen cart, for example with the Storefront API [`cartDiscountCodesUpdate`](https://shopify.dev/docs/api/storefront/latest/mutations/cartDiscountCodesUpdate) mutation:

## JavaScript

```javascript
const leadCapture = await sdk.create('lead-capture', {
  attributes: {
    buttonType: 'save',
    returnTo: 'same-page',
    storefrontOrigin: 'https://checkout.example.com',
  },
  async onAuthenticate() {
    const discount = {code: 'WELCOME10', gid: 'gid://shopify/DiscountCodeNode/123'};
    await applyDiscountCodeToCart(discount.code);
    return {discount};
  },
});
```

***

## Handle errors

Lead capture reports these errors on Hydrogen storefronts:

| Code | When it happens | What lead capture does |
| - | - | - |
| `storefront_origin_required` | The page isn't served by the online store, and `storefrontOrigin` isn't set or isn't a valid origin. | Renders nothing, instead of a button that leads to an error page. |
| `storefront_origin_unsupported_flow` | `storefrontOrigin` is set to another origin, and `buttonType` is `'none'`. | Still renders the embedded flow, which isn't supported on Hydrogen storefronts. Set `buttonType` to show a button. |
| `no_discount_received` | The SDK couldn't save the discount from `onAuthenticate` to the buyer's Shop account. | Completes sign-in. |

Each error reaches the feature's `onError` and the SDK-level `onError` passed to `initialize`, including a handler that you add or replace later with `update`:

## JavaScript

```javascript
const sdk = window.ShopSDK.initialize({
  apiKey: 'YOUR_CLIENT_ID',
  onError(event) {
    if (event.code === 'storefront_origin_required') {
      // Set storefrontOrigin to the https origin of your online store.
    }
    if (event.code === 'storefront_origin_unsupported_flow') {
      // Set buttonType to anything other than 'none'.
    }
  },
});
```

***

## Next steps

* See the [lead capture reference](https://shopify.dev/docs/api/shop-sdk/reference/lead-capture) for all attributes and events.
* Review the [lead capture](https://shopify.dev/docs/api/shop/guides/use-cases/lead-capture) guide for the full integration.

***
