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

# Build a vendor integration

> End-to-end quickstart for a vendor adding COPE checkout and webhook handling to an existing site, with a reference Next.js sample app.

# Build a vendor integration

A complete COPE integration is three pieces: a small amount of dashboard configuration, a buyer-facing checkout page, and a backend webhook receiver that confirms each payment.

<Tip>
  **Try it deployed in a few minutes**:
  [`copecart/integration-samples`](https://github.com/copecart/integration-samples)
  is a Next.js app with both checkout styles, a webhook receiver, and
  Vercel / Railway deploy buttons in the README.
</Tip>

## 1. Configure the business

In the COPE dashboard, gather three values before writing any code:

| Setting                              | Where                               | Purpose                                                                                                                             |
| ------------------------------------ | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Publishable key (`cope_pk_live_...`) | Settings → SDK                      | Safe to ship in the browser. Used by the Checkout SDK. Created with the SDK integration for the business.                           |
| Product UUID (`prod_...`)            | Catalog                             | The product the buyer will purchase.                                                                                                |
| Embed origin (iframe checkout only)  | Settings → Checkout → Embed domains | The exact parent origin that will host the iframe, for example `https://shop.example.com`.                                          |
| Redirect URLs                        | Settings → SDK                      | The complete `success_url` and `cancel_url` values you will pass to `checkout()`, for example `https://shop.example.com/thank-you`. |

Embed origins use scheme + host (+ port) only — no path, query, or fragment. `https://shop.example.com` and `https://www.shop.example.com` are different origins.

**Embed origins and `success_url` / `cancel_url` are independent allowlists.** Registering an origin authorizes a parent page to *iframe* the checkout (and registers the domain with Stripe Payment Method Domains so browser wallets work). The success and cancel URLs are registered separately, must be HTTPS, and are checked when the checkout is created.

The two use different matching rules: an embed origin is scheme + host (+ port), while a redirect URL is matched as a complete URL string including its path and any query string. Registering `https://shop.example.com` does **not** authorize `https://shop.example.com/thank-you`. See [redirect URLs](../checkout-sdk/overview#redirect-urls).

Failures look different too — an unregistered embed origin produces a browser CSP block (`frame-ancestors 'none'`), while an unregistered `success_url` produces a `422 invalid_redirect_url` from the checkout-creation call.

## 2. Add checkout to your page

Install the Checkout SDK. Iframe checkout requires `^0.2.0` or later for `mountCheckout`; hosted (redirect) checkout works on any `0.x`.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
pnpm add @copecart/sdk
```

The setup is the same for both checkout styles. Build a cart, add a line, create the checkout session — only the last call differs.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { CopeCart } from "@copecart/sdk"

const cope = new CopeCart({ publishableKey: "cope_pk_live_..." })

const cart = await cope.createCart({ currency: "EUR" })
await cope.addLine(cart.id, { product_id: "prod_..." })

const checkout = await cope.checkout(cart.id, {
  embed_origin: window.location.origin, // iframe only — omit for redirect
  success_url: "https://shop.example.com/thank-you",
  cancel_url: "https://shop.example.com/cart",
  consents: [{ type: "buyer_tos" }],
})
```

For hosted (redirect) checkout, send the buyer to COPE:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
cope.redirectToCheckout(checkout)
```

For embedded (iframe) checkout, add a container element to the page where the iframe should mount:

```html theme={"theme":{"light":"github-light","dark":"github-dark"}}
<div id="cope-checkout-frame"></div>
```

Then mount the checkout on it:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
cope.mountCheckout("#cope-checkout-frame", checkout, {
  fallback: "redirect",
  onReady: () => showCheckoutFrame(),
  onSuccess: () => showOrderConfirmation(),
  onError: ({ code, retryable }) => reportCheckoutError(code, retryable),
})
```

In both styles, COPE redirects the buyer to `success_url` once payment completes. Treat that landing page as cosmetic — the authoritative payment signal arrives on your backend as a webhook (next step).

See [Embedded hosted checkout](../checkout-sdk/embedded-checkout) for the full mount contract, every callback, and the security model. The [Checkout SDK overview](../checkout-sdk/overview) covers the cart-building APIs.

## 3. Receive signed webhooks

The thank-you page is decorative. A buyer can land there directly and the `order_uuid` in the URL can be forged. Treat the signed webhook delivery to your backend as the only authoritative payment signal — that is where you grant access, write to your database, and notify other systems.

Stand up an HTTPS endpoint and register it once with COPE using a server-side `cope_sk_...` key — that is the secret counterpart of the publishable `cope_pk_...` you put in the browser. Only the publishable key is safe to ship to a client; the secret key stays on your server and is rotated periodically. Once the endpoint is registered, COPE will POST every relevant event to your URL. The receiver should:

* Verify the `X-Cope-Signature` header (`t=<timestamp>,v1=<hmac>`) — the HMAC covers `"{timestamp}.{raw body}"`, not the body alone.
* Ack `2xx` immediately. Push real work to a queue.
* Dedupe by `X-Cope-Event-Id` — `2xx` does not guarantee at-most-once delivery.

[Consume webhooks](./consume-webhooks) shows the recommended receiver shape. [Webhook signing](../webhooks/signing) is the exact HMAC contract, and [Webhook event types](../webhooks/event-types) lists every event you might handle.

## Before going live

A handful of things easy to miss until the first real buyer trips on them:

* The webhook handler grants access — not the thank-you page.
* Rotate the secret API key (`cope_sk_...`) and the webhook signing secret on a schedule. Store both server-side only.
* For iframe checkout, every parent origin you deploy from — preview, staging, production, custom domains — must be registered separately under Settings → Checkout. They are different origins to the browser.
