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

# Theme Preview

> Embed a live checkout preview in your dashboard or theme editor without creating a real session

The `/preview` endpoint renders a fully interactive checkout page backed by a stub session — no database round-trip, no API keys required. It is designed for embedding inside a dashboard theme editor so merchants can see appearance changes in real time.

## URL

```
GET https://checkout.ozura.com/preview              # production
GET https://sandbox-checkout.ozura.com/preview      # sandbox
```

No authentication headers are required. The page is publicly accessible.

<Note>
  The sandbox preview serves the same stub session and accepts the same `postMessage` appearance updates as production — it's purely a visual preview either way. Whichever URL you embed, make sure the `postMessage` **target origin** (the second argument to `iframe.contentWindow.postMessage`) matches that URL exactly. Messages sent with a mismatched target origin are dropped silently.
</Note>

## Embed as an Iframe

```html theme={null}
<iframe
  id="checkout-preview"
  src="https://checkout.ozura.com/preview"
  style="width: 100%; height: 700px; border: none; border-radius: 12px;"
></iframe>
```

## Sending Appearance Updates

Once the preview iframe loads, send appearance changes via `postMessage`. The preview page listens for `ozura-preview-appearance` messages and applies them live using the same `applyAppearanceConfig` function used by real checkout sessions.

```js theme={null}
const iframe = document.getElementById('checkout-preview');

function updatePreview(appearance) {
  iframe.contentWindow.postMessage(
    { type: 'ozura-preview-appearance', appearance },
    'https://checkout.ozura.com'
  );
}

// Example: change primary button colour
updatePreview({ primaryColor: '#8b5cf6', borderRadius: 'lg' });
```

<Warning>
  Always specify the target origin (`'https://checkout.ozura.com'`) as the second argument to `postMessage`. Passing `'*'` allows any page to intercept the message.
</Warning>

### Appearance Message Shape

```json theme={null}
{
  "type": "ozura-preview-appearance",
  "appearance": {
    "primaryColor": "#8b5cf6",
    "backgroundColor": "#0f0a1a",
    "borderRadius": "lg",
    "fontFamily": "Inter, sans-serif"
  },
  "merchantName": "My Store"
}
```

| Field          | Type                   | Required | Description                                                                                                                                                              |
| :------------- | :--------------------- | :------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`         | string                 |  **Yes** | Must be `"ozura-preview-appearance"`                                                                                                                                     |
| `appearance`   | object                 |    No    | Any fields from the [Appearance Reference](/guides/payments/checkout/appearance-reference). Only the fields you include are updated; others stay at their current values |
| `merchantName` | string (max 200 chars) |    No    | Updates the merchant name shown in the checkout header in real time. Omit to leave the current name unchanged                                                            |

## Ready Event

The preview page fires `ozura-preview-ready` as soon as it mounts. Listen for this event before sending appearance updates to avoid messages being dropped before the listener is registered.

```js theme={null}
window.addEventListener('message', (event) => {
  if (
    event.origin === 'https://checkout.ozura.com' &&
    event.data?.type === 'ozura-preview-ready'
  ) {
    // Safe to send appearance now
    updatePreview(myCurrentAppearance);
  }
});
```

## Stub Session

The preview runs against a fixed in-memory session:

| Field         | Value                                     |
| :------------ | :---------------------------------------- |
| Amount        | `$49.99`                                  |
| Currency      | `USD`                                     |
| Mode          | `payment`                                 |
| Cart          | Single item — "Sample Product"            |
| Merchant name | `"Your Store"`                            |
| Status        | `pending` (payment form is fully visible) |

The **Pay** button is rendered and interactive but will fail gracefully if clicked — there is no real vault key or payment processor connection. This is intentional: the preview is for visual inspection only.

## Limitations

* Cannot test actual payments
* `successUrl`, `cancelUrl`, `errorUrl` are set to `#` — no redirect occurs
* The session ID is `"preview"` — it does not exist in the database
* Changes made via `postMessage` are in-memory only and reset on page reload
* The preview page **ignores** messages from opaque origins (`null`, `file://`, sandboxed iframes). The parent page must be served over HTTP/HTTPS with a real origin for `postMessage` to be accepted. The preview pins to the first origin it receives a message from — subsequent messages from a different origin are dropped.

## Full Integration Example

```html theme={null}
<!DOCTYPE html>
<html>
<head>
  <title>Theme Editor</title>
</head>
<body>
  <label>
    Primary Color
    <input type="color" id="primaryColor" value="#9693FE" />
  </label>

  <iframe
    id="checkout-preview"
    src="https://checkout.ozura.com/preview"
    style="width: 480px; height: 700px; border: none;"
  ></iframe>

  <script>
    const iframe = document.getElementById('checkout-preview');
    const primaryColorInput = document.getElementById('primaryColor');

    // Wait for the preview to signal it is ready
    window.addEventListener('message', (event) => {
      if (
        event.origin !== 'https://checkout.ozura.com' ||
        event.data?.type !== 'ozura-preview-ready'
      ) return;

      // Send initial appearance
      sendAppearance();
    });

    primaryColorInput.addEventListener('input', sendAppearance);

    function sendAppearance() {
      iframe.contentWindow.postMessage(
        {
          type: 'ozura-preview-appearance',
          appearance: { primaryColor: primaryColorInput.value }
        },
        'https://checkout.ozura.com'
      );
    }
  </script>
</body>
</html>
```
