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

# Embed address collection

> Add Numeral's secure address collector to your checkout with the numeral-checkout custom element.

Embedded collection keeps the address validation experience inside your checkout. Your server creates an embedded Bridge session with the `numeral-tax` SDK, and the browser mounts Numeral's `<numeral-checkout>` custom element using the returned session capability.

<Info>
  The collector appears inside your page, but its sensitive form runs in a sandboxed, cross-origin iframe served by Numeral. Your page receives state, requirement codes, and tax amounts—not the buyer's address or tax IDs.
</Info>

## 1. Allow your checkout origin

In **Developers → Numeral for Stripe Checkout**, add the exact origin of your checkout page to **Allowed embed origins**. For example:

```text theme={null}
https://store.example
```

Success and cancel URLs must also use an origin listed under **Allowed redirect origins**. Click **Save and publish** after changing either list.

## 2. Create an embedded session on your server

```ts theme={null}
import NumeralAPI from "numeral-tax";

const numeral = new NumeralAPI({
  apiKey: process.env.NUMERAL_API_KEY!,
});

export async function POST() {
  const session = await numeral.tax.bridge.sessions.create({
    config_id: process.env.NUMERAL_STRIPE_CHECKOUT_CONFIG_ID!,
    collection_mode: "embedded",
    confirmation_method: "automatic",
    checkout: {
      mode: "payment",
      line_items: [{ price: "price_123", quantity: 1 }],
      success_url: "https://store.example/success",
      cancel_url: "https://store.example/cart",
    },
    tax_context: {
      location: {
        basis: "billing_address",
        address: { country: "US" },
      },
    },
    "X-API-Version": "2026-03-01",
    "Idempotency-Key": crypto.randomUUID(),
  });

  return Response.json({
    sessionId: session.id,
    clientSecret: session.client_secret,
  });
}
```

<Warning>
  The Numeral API key stays on your server. Only the session ID and session-scoped `client_secret` are sent to the browser.
</Warning>

## 3. Load and mount the collector

Load the versioned element loader once on your checkout page:

```html theme={null}
<script
  type="module"
  src="https://checkout.numeralhq.com/bridge/v1.js"
></script>
```

Then create the element after your server returns the embedded session:

```html theme={null}
<div id="numeral-address-step"></div>

<script type="module">
  const response = await fetch("/api/checkout", { method: "POST" });
  const { sessionId, clientSecret } = await response.json();

  await customElements.whenDefined("numeral-checkout");

  const checkout = document.createElement("numeral-checkout");
  checkout.setAttribute("session-id", sessionId);
  checkout.setAttribute("presentation", "inline");
  checkout.setAttribute("redirect", "auto");

  // Keep the capability in memory. Do not serialize it into an HTML attribute.
  checkout.clientSecret = clientSecret;

  document.querySelector("#numeral-address-step").append(checkout);
  await checkout.open();
</script>
```

The element asks only for fields Numeral still needs. After the location is validated and tax is calculated, it redirects the buyer to the prepared Stripe Checkout Session.

## Presentation options

Set `presentation` to control how the collector appears:

| Value    | Behavior                                                    |
| -------- | ----------------------------------------------------------- |
| `inline` | Renders the collector in the element's position on the page |
| `modal`  | Opens the collector in a dialog                             |
| `button` | Renders a trigger button that opens the collector           |

Use `redirect="auto"` to continue to Stripe automatically. Use `redirect="manual"` when your application needs to navigate itself. In manual mode, listen for both redirect paths:

* `numeral-session-created` includes `detail.url` when the session can continue directly to Stripe without collecting more information.
* `numeral-before-redirect` includes `detail.url` after the embedded collector finishes collecting the required information.

## Events

The element emits bubbling, composed custom events with redacted details:

| Event                     | When it fires                                                                                                                                |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `numeral-session-created` | The session is loaded or its Stripe Checkout Session is created; in manual mode, includes the provider URL when no collection step is needed |
| `numeral-ready`           | The embedded collector is ready                                                                                                              |
| `numeral-state-change`    | The session state or required fields change                                                                                                  |
| `numeral-tax-calculated`  | Tax is available; includes currency and total tax amount                                                                                     |
| `numeral-before-redirect` | Immediately before navigation to Stripe; cancelable in manual flows                                                                          |
| `numeral-error`           | A safe client-facing error occurs                                                                                                            |
| `numeral-cancel`          | The buyer cancels or closes collection                                                                                                       |

When using `redirect="manual"`, handle the provider URL from either redirect path:

```js theme={null}
function redirectToProvider(event) {
  if (event.detail.url) {
    window.location.assign(event.detail.url);
  }
}

checkout.addEventListener("numeral-session-created", redirectToProvider);
checkout.addEventListener("numeral-before-redirect", redirectToProvider);
```

You can listen for the remaining lifecycle events as needed:

```js theme={null}
checkout.addEventListener("numeral-tax-calculated", (event) => {
  const { currency, totalTaxAmount } = event.detail;
  updateOrderSummary({ currency, tax: totalTaxAmount });
});

checkout.addEventListener("numeral-error", (event) => {
  showCheckoutError(event.detail.code);
});

checkout.addEventListener("numeral-cancel", () => {
  showCart();
});
```

## Appearance

The host element supports CSS custom properties for basic visual alignment with your checkout:

```css theme={null}
numeral-checkout {
  display: block;
  --numeral-bridge-color-primary: #173f2d;
  --numeral-bridge-font-family: Inter, system-ui, sans-serif;
  --numeral-bridge-border-radius: 10px;
}
```

## Content Security Policy

If your site uses a Content Security Policy, allow Numeral's loader, collector frame, and client API:

```text theme={null}
script-src https://checkout.numeralhq.com
frame-src https://checkout.numeralhq.com
connect-src https://api.numeralhq.com
```

Merge these sources with your existing policy rather than replacing it.

## Security checklist

* Create Bridge sessions only from your server.
* Never expose a Numeral `sk_test_...` or live secret key in browser code.
* Assign `clientSecret` as a JavaScript property, never an HTML attribute or URL parameter.
* Do not log or persist `client_secret`.
* Allow only exact merchant origins that need to embed the collector.
* Treat collector events as lifecycle signals; buyer PII is intentionally absent.
* Reuse an idempotency key only for the same create-session request.
