> For the complete documentation index, see [llms.txt](https://plexo.gitbook.io/rest-api/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://plexo.gitbook.io/rest-api/sessions/embedded-checkout.md).

# Embedded Checkout Integration

Use embedded checkout when you want Plexo to render the secure checkout inside your e-commerce page instead of sending the payer to a separate hosted page.

Your backend still creates the checkout session with `POST /v1/sessions`. The difference is that your frontend selects the `EMBEDDED` action, places its `href` in an iframe, and exchanges versioned browser messages with Plexo.

{% hint style="info" %}
Embedded checkout is currently available for integration testing in the Testing environment. Production framing remains disabled until Plexo enables the production iframe policy and your merchant origins.
{% endhint %}

## Integration flow

```mermaid
sequenceDiagram
  participant Backend as Your backend
  participant Store as Your e-commerce frontend
  participant Checkout as Plexo checkout iframe
  participant Plexo as Plexo API

  Backend->>Plexo: POST /v1/sessions (type=checkout)
  Plexo-->>Backend: SessionDto with REDIRECT and EMBEDDED actions
  Backend-->>Store: EMBEDDED action href
  Store->>Checkout: Load href unchanged in iframe
  Store->>Checkout: checkout-init (postMessage)
  Checkout-->>Store: checkout-ready
  Checkout-->>Store: Lifecycle and redirect events
  Plexo-->>Backend: Payment callback
```

Callbacks remain the source of truth for your order state. Browser messages control the payer experience but do not replace server-to-server payment reconciliation.

## Step 1: Create a checkout session

Create the session from your backend with the same endpoint used for full-page hosted checkout.

```http
POST /v1/sessions
Authorization: Basic {credentials}
Content-Type: application/json

{
  "merchantId": 12345,
  "referenceId": "checkout-session-1001",
  "type": "checkout",
  "paymentRequest": {
    "referenceId": "payment-1001",
    "invoiceNumber": "INV-1001",
    "installments": 1,
    "amount": {
      "total": 100.00,
      "currency": "UYU"
    }
  },
  "settings": {
    "callbacks": {
      "paymentCallbackUrl": "https://merchant.example.com/callbacks/payments"
    },
    "redirects": {
      "defaultUrl": "https://merchant.example.com/orders/1001",
      "cancelUrl": "https://merchant.example.com/cart"
    }
  }
}
```

Plexo returns two `self` actions:

```json
{
  "actions": [
    {
      "rel": "self",
      "href": "https://checkout.testing.plexo.com.uy/{sessionId}?t={sessionToken}",
      "method": "REDIRECT"
    },
    {
      "rel": "self",
      "href": "https://checkout.testing.plexo.com.uy/e/{sessionId}?t={sessionToken}",
      "method": "EMBEDDED"
    }
  ]
}
```

* Use `method = REDIRECT` for a full-page hosted flow.
* Use `method = EMBEDDED` for the iframe flow.

Use the returned `href` unchanged. It contains a short-lived verification token. Do not construct, modify, log, or persist this URL.

## Step 2: Create the iframe and initialize messaging

Select the embedded action on your frontend and restrict browser messages to the checkout origin.

```html
<iframe
  id="plexo-checkout"
  title="Secure payment"
  allow="payment"
  style="width: 100%; min-height: 720px; border: 0"
></iframe>
```

```js
const embeddedAction = session.actions.find(
  (action) => action.rel === "self" && action.method === "EMBEDDED",
);

if (!embeddedAction) {
  throw new Error("Embedded checkout is not available for this session");
}

const checkoutFrame = document.getElementById("plexo-checkout");
const checkoutOrigin = new URL(embeddedAction.href).origin;

checkoutFrame.addEventListener("load", () => {
  checkoutFrame.contentWindow.postMessage(
    {
      plexo: {
        version: 1,
        event: "checkout-init",
      },
    },
    checkoutOrigin,
  );
});

checkoutFrame.src = embeddedAction.href;
```

The checkout validates that `checkout-init` came from its direct parent. In Testing, parent origins are not restricted so merchants can integrate from localhost and temporary development domains. Production will accept only origins configured by Plexo. Contact Plexo to enable your e-commerce origins before launch.

After initialization, Plexo binds the exact parent origin and replies with:

```json
{
  "plexo": {
    "version": 1,
    "event": "checkout-ready",
    "allowIframe": true
  }
}
```

## Step 3: Handle checkout events

Validate both `event.origin` and `event.source` before using a message.

```js
window.addEventListener("message", (event) => {
  if (
    event.origin !== checkoutOrigin ||
    event.source !== checkoutFrame.contentWindow
  ) {
    return;
  }

  const message = event.data?.plexo;
  if (message?.version !== 1) {
    return;
  }

  switch (message.event) {
    case "height":
      // Use the numeric height to resize the iframe.
      break;

    case "checkout-ready":
      // The iframe-parent channel is ready.
      break;

    case "checkout-pending":
      // Keep the order pending and wait for your payment callback.
      break;

    case "checkout-completed":
      // Show a completion state, then confirm it through your backend.
      break;

    case "checkout-expired":
      // Show that the checkout session or payment has expired.
      break;

    case "checkout-failed":
      // Let the payer retry or return to your checkout.
      break;

    case "checkout-cancelled":
    case "checkout-redirect":
      if (message.redirectUrl) {
        window.location.assign(message.redirectUrl);
      }
      break;
  }
});
```

| Event                | Meaning                                          | Recommended merchant action                         |
| -------------------- | ------------------------------------------------ | --------------------------------------------------- |
| `height`             | The latest iframe content height as a number     | Use the value to size the iframe                    |
| `checkout-ready`     | Parent messaging is initialized                  | Mark the iframe as ready                            |
| `checkout-pending`   | A payment exists and is still pending            | Keep the order pending and wait for callbacks       |
| `checkout-completed` | Checkout observed an approved payment            | Show confirmation, then verify in your backend      |
| `checkout-expired`   | Checkout observed an expired session or payment  | Show an expiration state and verify in your backend |
| `checkout-failed`    | Checkout observed a terminal unsuccessful status | Show retry or failure UX                            |
| `checkout-cancelled` | Plexo successfully cancelled the session         | Navigate to `redirectUrl` when present              |
| `checkout-redirect`  | The payer must continue outside the iframe       | Navigate the top-level window to `redirectUrl`      |

Messages never contain card data, payment-session credentials, or raw provider payloads.

## Result-page redirect behavior

The `settings.redirects.skipResultPage` setting controls whether Checkout skips its hosted result UI after a terminal payment result. It defaults to `false`. When it is `true`, Checkout skips the result UI for terminal success and, when `redirectOnError` is `true`, for terminal unsuccessful results as well. When it is `false`, Checkout shows the hosted result UI and may automatically navigate the iframe to the redirect target after its result countdown. This hosted-result auto-navigation does not emit `checkout-redirect`.

The `settings.redirects.redirectOnError` setting defaults to `true`. Set it to `false` to keep denied, failed, and expired results on the hosted result UI. This setting does not affect provider or 3DS continuation redirects.

In embedded mode, when `skipResultPage` is `true` and a result redirect applies, Checkout sends the terminal `checkout-*` event before `checkout-redirect`. The parent owns navigation and must handle the terminal event first, then navigate the top-level window only after receiving `checkout-redirect`. Checkout does not automatically navigate the parent page from the iframe.

## Redirect-based payment methods

All checkout payment methods remain available in embedded mode. When a payment method needs a provider, bank, 3DS, or approval page that cannot run in an iframe, Plexo emits `checkout-redirect`. Your page must navigate its top-level window to the supplied `redirectUrl`.

Sistarbanc is handled directly by checkout: its signed form targets the top-level window because home-banking sites generally reject iframe embedding. After the bank returns, Plexo restores the checkout session without relying on the iframe's cookies.

If you send `"skipResultPage": true` in `settings.redirects`, Sistarbanc redirects directly to `settings.redirects.defaultUrl` after the return flow.

## Pending Abitab and Redpagos payments

By default, embedded checkout shows the pending instructions page and continues polling the payment. Closing or removing the iframe does **not** cancel an Abitab or Redpagos payment. Keep your order pending and use `paymentCallbackUrl` to reconcile later status changes.

If your e-commerce site already has its own pending-payment page, you can skip Plexo's page:

```json
{
  "settings": {
    "checkout": {
      "skipPendingCashPage": true
    },
    "redirects": {
      "defaultUrl": "https://merchant.example.com/orders/1001/pending"
    }
  }
}
```

This setting:

* applies only to embedded Abitab and Redpagos checkout
* defaults to `false`
* requires `settings.redirects.defaultUrl`
* keeps the payment active in `pending`
* uses `paymentRequest.expirationMinutes` for the payment-code lifetime, defaulting to `1440` minutes
* emits `checkout-redirect` after Plexo creates the payment code

If `defaultUrl` is empty, checkout keeps the Plexo pending page instead.

## Back and cancel behavior

* Checkout's internal back control returns to the previous checkout step inside the iframe.
* Browser Back returns through the iframe's checkout history without navigating the merchant page.
* **Cancel and return** calls Plexo's cancellation endpoint. Plexo emits `checkout-cancelled` only after the cancellation succeeds.
* Removing the iframe is not a cancellation signal. Do not assume an abandoned iframe means the payment or session was cancelled.

## Security checklist

* Create sessions only from your backend; never expose API credentials in browser code.
* Use the returned action `href` unchanged.
* Send `postMessage` to the exact checkout origin, never `"*"`.
* Accept messages only from the expected checkout origin and iframe window.
* Treat every redirect URL as navigation data, not as HTML or executable code.
* Do not put card data, API credentials, or payment tokens in parent messages.
* Reconcile orders from server-to-server callbacks or API queries, not browser events alone.

## Related resources

* [Checkout Session Integration](/rest-api/sessions/checkout-session.md)
* [Callbacks](/rest-api/operations/callbacks.md)
* [Payment Processing Guide](/rest-api/payments/payment-processing.md)
* [Testing](/rest-api/operations/testing.md)
* [SessionDto](/rest-api/api-reference/models.md#sessiondto)
