# Web checkout

> Load Bolt Checkout on a web storefront and open it with an order token from your backend.

Source: https://help.boltapp.com/platforms/direct-api

Your storefront loads Bolt's hosted browser runtime and requests a fresh order token from your backend when a shopper opens checkout.

<span id="pre-requisites" />

## Before you add the client [#before-you-add-the-client]

You need:

| Requirement                                                      | Where it belongs   |
| ---------------------------------------------------------------- | ------------------ |
| Publishable key for the selected division and environment        | Browser            |
| An endpoint in your application that returns a fresh order token | Your backend       |
| The storefront host registered as a Trusted Domain               | Merchant Dashboard |

Complete [prerequisites and readiness](/custom-cart/prerequisites) and [create an order token](/custom-cart/create-order-token) before connecting a real cart.

<Warning>
  Never put a Bolt API key or signing secret in browser code. The browser calls your backend for an order token; your backend calls Bolt with the API key.
</Warning>

<span id="api-keys" />

<span id="environments" />

<span id="configure-your-integration" />

## Load the hosted scripts [#load-the-hosted-scripts]

Add both scripts to pages that can open checkout. Use the hosts for the same environment as your publishable key.

<CodeGroup>
  <CodeBlockTabs defaultValue="Sandbox">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="Sandbox">
        Sandbox
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="Production">
        Production
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="Sandbox">
      ```html
      <script
        async
        id="bolt-track"
        src="https://connect-sandbox.boltapp.com/track.js"
        data-publishable-key="YOUR_SANDBOX_PUBLISHABLE_KEY"
      ></script>
      <script
        id="bolt-connect"
        src="https://connect-sandbox.boltapp.com/connect.js"
        data-publishable-key="YOUR_SANDBOX_PUBLISHABLE_KEY"
      ></script>
      ```
    </CodeBlockTab>

    <CodeBlockTab value="Production">
      ```html
      <script
        async
        id="bolt-track"
        src="https://connect.boltapp.com/track.js"
        data-publishable-key="YOUR_PRODUCTION_PUBLISHABLE_KEY"
      ></script>
      <script
        id="bolt-connect"
        src="https://connect.boltapp.com/connect.js"
        data-publishable-key="YOUR_PRODUCTION_PUBLISHABLE_KEY"
      ></script>
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

Keep `async` and `defer` off the `connect.js` tag and load it before the application code that calls Bolt. The script creates `window.BoltCheckout` before the browser continues to the next script. `track.js` can remain asynchronous because checkout does not call it directly.

## Open checkout from your button [#open-checkout-from-your-button]

Add a button that your application owns:

```html title="HTML"
<button id="bolt-checkout-button" type="button" disabled>
  Checkout with Bolt
</button>
```

The example below verifies that the hosted script initialized, requests a token from your backend, configures checkout once for that attempt, and opens the modal.

```javascript title="JavaScript"
async function initializeBoltButton() {
  const checkoutButton = document.querySelector("#bolt-checkout-button");
  const BoltCheckout = window.BoltCheckout;

  if (!BoltCheckout?.configure) {
    throw new Error("Bolt Checkout did not load");
  }

  checkoutButton.disabled = false;

  checkoutButton.addEventListener("click", async () => {
    checkoutButton.disabled = true;

    try {
      const response = await fetch("/api/checkout/order-token", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ cartId: "CURRENT_CART_ID" }),
      });

      if (!response.ok) {
        throw new Error(`Order token request failed: ${response.status}`);
      }

      const { token } = await response.json();
      const configuredCheckout = BoltCheckout.configure(
        { orderToken: token },
        {},
        {
          success: (_transaction, complete) => complete(),
        },
      );

      if (!configuredCheckout?.open) {
        throw new Error("Bolt Checkout could not be configured");
      }

      configuredCheckout.open();
    } finally {
      checkoutButton.disabled = false;
    }
  });
}

initializeBoltButton().catch(console.error);
```

`/api/checkout/order-token` is an endpoint in your application, not a Bolt URL. It should build the current cart and call `POST /v1/merchant/orders` from your server as shown in [Create an order token](/custom-cart/create-order-token).

<Note>
  The browser `success` callback is useful for interface updates, but it is not a trusted payment record. Reconcile orders from your verified server-side callback and webhook handling.
</Note>

## Browser callback behavior [#browser-callback-behavior]

The callback object passed to `configure` is optional. If you provide `success`, call its completion function after your client-side work finishes so checkout can continue. [Frontend events](/developers/frontend-events#bolt-checkout) lists every callback, when Bolt calls it, and the `onNotify` events checkout sends.

Do not make the browser callback your only record of an order. When an `order.create` Merchant Callback response contains `order_received_url`, Bolt redirects the shopper there and the browser `success` and `close` callbacks do not run. Use server-side callbacks and webhooks for durable processing.

<span id="the-four-pieces" />

## Verify the web client [#verify-the-web-client]

In sandbox:

1. Load the page and confirm `track.js` and `connect.js` return successfully.
2. Select your checkout button and confirm your backend returns an order token.
3. Confirm the Bolt modal opens and its total matches the cart sent by your backend.
4. Complete a checkout with a [sandbox test card](/getting-started/test-cards), then confirm your Merchant Callback handlers receive the expected events.

If the modal does not open or reports `cart_error`, continue to [Custom cart troubleshooting](/custom-cart/troubleshooting).

## SDK boundaries [#sdk-boundaries]

The web client does not install an npm package. In particular:

* `@boltpay/bolt-js` supports Bolt Gaming payment-link flows, not this Custom Cart integration.
* `@boltpay/bolt-typescript-sdk` is a server-side generated API client. Its Orders API is not the Custom Cart `POST /v1/merchant/orders` order-token contract.

Use the hosted scripts above for the browser and keep the Custom Cart REST call on your backend.

<span id="next" />
