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

# Connect

> Embed BoomFi checkout on your own page with @boomfi-sdk/connect-web.

Connect embeds the same checkout as [Checkout](/payments/checkout) in an overlay on your own page, instead of redirecting the customer to `https://pay.boomfi.xyz`. The checkout runs in an iframe served by BoomFi: payment details, wallet connections, and payment state never touch your page.

## Choosing a UI mode

A checkout session's `ui_mode` picks one of three ways to present the same checkout:

| `ui_mode` | Where it runs | How the customer returns |
| - | - | - |
| `Hosted` | Full page on `https://pay.boomfi.xyz` | `success_url` / `cancel_url` redirect |
| `Lite` | Same lightweight checkout as `Embedded`, but as its own standalone page — not framed | `success_url` / `cancel_url` redirect |
| `Embedded` | The same lightweight checkout, framed inside your page via `@boomfi-sdk/connect-web` | Stays on your page; you get `return_url` instead |

This page covers `Embedded`, which is the default `ui_mode` for a session and what `@boomfi-sdk/connect-web` opens. For `Hosted`, see [Checkout](/payments/checkout). `Lite` is the standalone form of the same lightweight checkout, for when you want that UI without embedding it — ask your BoomFi contact for the setup if you're choosing `Lite`, since it isn't covered by the rest of this page.

## 0. Get Connect enabled for your org

Connect is enabled per organisation for pilot integrations, and nothing below this line is reachable until it is: every `/checkout/*` call 404s, and the **Checkout** tab under Settings doesn't even appear in your dashboard's navigation. Contact your BoomFi representative first and confirm they've turned it on before you try anything else.

## How it fits together

1. You register the origin your checkout page is served from
2. Your server creates a checkout session and gets back a client secret
3. Your page installs `@boomfi-sdk/connect-web` and opens the overlay with that secret
4. The customer completes payment inside the overlay
5. Your backend receives a webhook and updates order state

## 1. Register a checkout origin

Once per org, before the first session — the origin your checkout page is served from. Add it in `https://app.boomfi.xyz/dashboard/settings/checkout`, under **Origins**:

<img src="https://mintcdn.com/boom-fi/ooP1OD7cz2Xd-4n4/payments/images/checkout-origins-register-boomfi.png?fit=max&auto=format&n=ooP1OD7cz2Xd-4n4&q=85&s=6dab01320cd7d50c6070411ce6459fd5" alt="Register checkout origin dialog" width="1352" height="769" data-path="payments/images/checkout-origins-register-boomfi.png" />

Or from your server:

```bash theme={null}
curl -X POST "https://mapi.boomfi.xyz/v1/checkout/origins" \
  -H "X-API-KEY: sk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "https://shop.example"
  }'
```

`origin` must be a bare `scheme://host[:port]` — no path, query, fragment, wildcard, or IP literal. Register every origin you serve a checkout page from (`www`, `checkout`, staging) separately. Re-registering an origin you already have re-enables it rather than failing.

If your site sends a Content Security Policy, also allow the checkout as a frame source, or the overlay opens empty with a `frame-ancestors` error in the console:

```
frame-src https://pay.boomfi.xyz;
```

Use `https://pay-test.boomfi.xyz` for the test environment.

## 2. Create a checkout session

From your server, never your page — it needs your API key:

```bash theme={null}
curl -X POST "https://mapi.boomfi.xyz/v1/checkout/sessions" \
  -H "X-API-KEY: sk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "payment_link_id": "2ZxK1qLm9vQnR7sT4uY6wB8cD0e",
    "allowed_origin": "https://shop.example",
    "merchant_request_id": "dep_92817"
  }'
```

### Notable fields

| Field | Description |
| - | - |
| `payment_link_id` | Required for the overlay to have a price to render. |
| `allowed_origin` | Omit when you have registered exactly one origin; required when you have registered more than one. Must match a registered origin. |
| `merchant_request_id` | Your idempotency key. Repeating one with the same parameters returns the existing session with `client_secret` absent. |
| `customer_reference` | Your identifier for the payer. Send a distinct one per payer — two concurrent payers on one payment link can otherwise collide. |
| `amount` / `currency` | Only if set, and must match the payment link's own price. |

`client_secret` in the response is returned once, by this call only — it is stored only as a digest, so a repeated `create` returns the session without it. Pass it straight to your page; do not log it.

## 3. Install the SDK and open the overlay

```bash theme={null}
npm install @boomfi-sdk/connect-web
```

```javascript theme={null}
import { init } from "@boomfi-sdk/connect-web";

const connect = init({ environment: "test" }); // "production" once you're live

// clientSecret is what your server got back in step 2.
connect.open({
  clientSecret,
  onReady: () => console.log("checkout visible"),
  onClose: ({ reason, resumable }) => {
    // resumable: true means a payment was in flight — offer a way back with
    // connect.resume(). Ask your server what happened; do not infer it from
    // reason alone (see the note below).
  },
  onError: ({ message, code }) => {
    // The overlay stays open showing `message` — see Troubleshooting below.
  },
});
```

`environment` must match where you created the session — a `test`-environment secret opened against `production` (or the reverse) fails with `session_not_found`.

<Note>
  A payer who just paid and closed the overlay, and one who changed their mind, both arrive at `onClose` as `reason: "user_requested"`, `resumable: false` — identical. Treating that as "cancelled" tells someone who paid that they did not. Say something true either way ("checking your payment") and confirm from your server (see [Confirm the payment](#confirm-the-payment)).
</Note>

Call `connect.canResume` on page load, before the payer has to ask — a payer can return to a live checkout up to 30 days after it was created if a payment is attached, so this is not only for a page reload moments later.

### Modal or inline

By default `open()` draws a modal: backdrop, page scroll lock, focus trap, and a close button. Pass `container` to mount the checkout inline into an element on your own page instead — no backdrop, no scroll lock, no focus trap, no close button. The frame fills whatever size and position `container` already has, the same as any other iframe you embed yourself:

```javascript theme={null}
connect.open({
  clientSecret,
  container: document.getElementById("checkout-slot"),
});
```

Add `unstyled: true` to go one step further and drop the SDK's own sizing and loading indicator too — a bare `<iframe>` you size and load-state entirely yourself:

```javascript theme={null}
connect.open({
  clientSecret,
  container: document.getElementById("checkout-slot"),
  unstyled: true,
});
```

Inline mount trades away what the overlay's own UI would otherwise do for you:

* **No close affordance.** There's no backdrop or button to close it with — the only way to take the checkout down is your own code calling `connect.close()` or `connect.destroy()`.
* **No confirm-before-close on a payment in flight.** The overlay's "your payment is still in progress" prompt has nowhere to render without a backdrop, so `close()`/`destroy()` act immediately, with no confirmation, exactly as today. `onClose` still fires with `reason: "merchant_requested"`, `resumable: true` — decide what to tell the customer from that, same as you would for the overlay's `resumable`.
* **`resume()` needs the container again.** Only the client secret survives a page reload; a DOM element cannot. Pass `container` to `connect.resume({ container })` the same way you did to `open()`, or you get the default modal back instead of your inline slot.

## What the customer sees

Connect's pilot payment rail is Wallet Transfer: the customer picks a chain and token from the ones your payment link's plan quotes, and the checkout shows a deposit address (and QR code) to send the payment to. There is nothing your page does here — it is entirely inside the BoomFi iframe.

The checkout polls for the on-chain deposit and moves to a success state once it confirms. Confirmation time is chain-dependent and can run from minutes to several hours, which is why a session with a payment attached stays usable well past its `expires_at` — a customer who left to fund a wallet can come back to a live checkout later.

## Confirm the payment

Treat your webhook as the only source of truth for whether the payment succeeded — see [Verify Webhook Signatures](/webhooks/verify-signatures) and [Event Types](/webhooks/event-types). Fulfil the order only on `Payment.Updated` with `status: Succeeded`; `onReady`, `onClose`, and `onError` are UI signals only and are lost if the tab closes or the connection drops mid-payment.

The webhook does not carry the checkout session's ID, so it alone does not tell you which of your own orders it belongs to. Read the session back to get the join key:

```bash theme={null}
curl "https://mapi.boomfi.xyz/v1/checkout/sessions/{sessionId}" \
  -H "X-API-KEY: sk_test_xxx"
```

* After `onClose`, read the session and store its `payment_id` against your order — that is what a later webhook joins on.
* For a payer who never comes back, run this on a schedule against sessions that are still `Open` with a payment attached, so a deposit that lands after the tab closed still reaches the right order.

A session with a `payment_id` and `status` still `Open` is a payment in flight, not a failure.

## Troubleshooting

| Symptom | Cause | Fix |
| - | - | - |
| Every `/checkout/*` call returns 404, valid API key | Connect is not enabled for this org yet | Contact BoomFi to enable it for your org |
| `POST /checkout/sessions` returns 400 with `origin_not_registered` | The org has no registered origin, or the one sent is not one of them | Register it (step 1); check scheme, host, and port match exactly |
| Overlay opens but stays empty, console shows a `frame-ancestors` error | The page's real origin does not match the session's `allowed_origin` | Recreate the session with the origin the page is actually served from |
| Overlay opens empty, no `frame-ancestors` error, own console error instead | Your page's Content Security Policy has no `frame-src` for the checkout | Add `frame-src https://pay.boomfi.xyz;` (`pay-test` for test) |
| `onError` fires `session_not_open` | The session already ended (completed or expired with no payment attached) | Create a new session — an ended one cannot be reopened |
| `onError` fires `session_not_found` | `clientSecret` names a session that does not exist in this `environment` | Check `init({ environment })` matches where the session was created |
| `onError` fires `client_secret_invalid` | The string passed to `open()` is not a `client_secret` BoomFi issued | Pass the `client_secret` value from step 2 verbatim, not the session `id` |
| Overlay shows a generic error inside the frame, no `onError` fires | The payment link's plan behind the session has expired | Check the payment link/plan status — this is not reported through the SDK |
| `connect.resume()` returns `false` even though the payment is still going, well after `expires_at` | The SDK drops its stored session at `expires_at`, regardless of the 30-day funded-session window the server keeps | Keep `client_secret` on your server and call `open()` with it again, rather than relying on `resume()` |

## Next steps

* [Checkout](/payments/checkout) — the redirect-based alternative to Connect
* [Webhooks Overview](/webhooks/overview)
* [Event Types](/webhooks/event-types)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.