# Hosted checkout & popup | Vitix Pay

> Every Vitix Pay checkout has a Vitix-hosted page. Redirect the customer to it, or open it in a popup window and listen for the completion message so the customer never leaves your page.

Canonical: https://vitix.net/developers/embed
Markdown: https://vitix.net/developers/embed.md


Documentation menu

Every checkout comes with a page on vitix.net that handles cards, wallets and 3-D Secure. Send the customer there with a redirect, or open it in a popup window and let it tell your page when the payment is done.

## The checkout url
`checkout.url` always points to `https://vitix.net/pay?cs=<checkout id>`. In live mode the page shows the card form; in test mode it shows the sandbox checkout where you can complete or decline the payment. The page is brand-neutral: it displays the amount, your `description` and `reference`, and nothing about your internal customer ids or metadata.
When the customer pays, the page shows Vitix's confirmation and keeps the customer there; it never redirects to your site and shows no link back to it. With the popup flow your page receives the `success` message and decides where to go next. The `payment.succeeded` webhook remains the source of truth for crediting the account.

## Option A — redirect
The simplest integration and the most robust one in every browser: send the customer to `checkout.url` with a 303 and let the hosted page do the rest.
ts

```
// Server: create the checkout, then send the browser to checkout.urlconst checkout = await vitix.checkouts.create({ amount: 2000, customer: { id: user.id, email: user.email }, success_url: "https://app.example.com/billing/done?cs={CHECKOUT_ID}", cancel_url: "https://app.example.com/billing",});redirect(checkout.url); // → https://vitix.net/pay?cs=cs_live_…
```

## Option B — popup window
Open `checkout.url` with `window.open` to keep the customer on your page. The hosted page posts a message to the window that opened it at each step and closes itself after a successful payment. Always check `e.origin === "https://vitix.net"` before trusting a message, and fall back to a redirect when the popup is blocked.
text

```
<!-- Client: open the Vitix checkout in a popup and keep your page underneath --><button id="topup">Add credits</button><script> document.getElementById("topup").addEventListener("click", async () => { // 1. Ask your server for a checkout (it calls vitix.checkouts.create and returns { id, url }) const { url } = await fetch("/api/topup", { method: "POST" }).then((r) => r.json()); // 2. Open the hosted page as a popup; fall back to a redirect if popups are blocked const w = window.open(url, "vitix-checkout", "popup=yes,width=600,height=900"); if (!w) { window.location.href = url; return; } // 3. Listen for the result window.addEventListener("message", function onMessage(e) { if (e.origin !== "https://vitix.net") return; // only trust Vitix const msg = e.data; if (!msg || msg.source !== "vitix-pay") return; if (msg.event === "success") { window.removeEventListener("message", onMessage); // Show a success state now; credit the account from the payment.succeeded webhook. window.location.href = "/billing/done?cs=" + encodeURIComponent(msg.checkout_id); } else if (msg.event === "close") { // Customer abandoned the payment form — show your own "try again" UI. } }); });</script>
```
The same thing as a React component:
ts

```
"use client";import { useCallback, useEffect, useRef, useState } from "react";export function TopupButton() { const [done, setDone] = useState<string | null>(null); const win = useRef<Window | null>(null); useEffect(() => { const onMessage = (e: MessageEvent) => { if (e.origin !== "https://vitix.net" || e.data?.source !== "vitix-pay") return; if (e.data.event === "success") setDone(e.data.checkout_id); }; window.addEventListener("message", onMessage); return () => window.removeEventListener("message", onMessage); }, []); const open = useCallback(async () => { const { url } = await fetch("/api/topup", { method: "POST" }).then((r) => r.json()); win.current = window.open(url, "vitix-checkout", "popup=yes,width=600,height=900"); if (!win.current) window.location.href = url; // popup blocked → redirect }, []); return done ? <p>Thanks! Your credits are on the way.</p> : <button onClick={open}>Add credits</button>;}
```

## Messages posted by the hosted page

| event | When | Payload |
| --- | --- | --- |
| loaded | The payment form is ready. | { source: 'vitix-pay', event: 'loaded', checkout_id } |
| success | The payment was confirmed. Credits follow via webhook within seconds. | { source: 'vitix-pay', event: 'success', checkout_id } |
| close | The customer closed or abandoned the payment form. | { source: 'vitix-pay', event: 'close', checkout_id } |
After `success` the popup closes itself. If the browser refuses to close it, the page shows a “Continue” button that leads to Vitix's confirmation page. Your page still receives the message, so handle `success` there.

## Why not an iframe?
The card form's security policy only allows `vitix.net` as a frame ancestor, and browsers check every ancestor in the chain. A page that embeds `vitix.net/pay` in an `<iframe>` would therefore see the form refuse to load. Use the popup instead: it is a top-level window, so the policy is satisfied and the customer still stays on your page.

## Testing
Create a checkout with a `vx_test_` key, open its `url` as a popup, click **Pay** in the sandbox form and confirm that your listener receives `success` and your webhook receives `payment.succeeded`. Click **Simulate a declined card** to exercise your error state. See [Testing](https://vitix.net/developers/testing).

[← Node SDK](https://vitix.net/developers/sdk)[Webhooks →](https://vitix.net/developers/webhooks)
Questions or a missing feature? Email [support@vitix.net](mailto:support@vitix.net)
