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

# Embed a Payment Link

> Embed a Compago payment link directly into your website via iframe.

Every payment link can be served two ways:

* **Compago-hosted page** — `https://app.compago.com/checkout/pl/{id}` (the canonical URL you share with customers).
* **Embeddable iframe** — `https://app.compago.com/checkout/pl/{id}/embed` (renders only the checkout form so it fits inside your own site).

The embedded URL is opt-in per integration: the same payment link can be used in both modes. There's no setting on the link itself — just point your `<iframe>` at the `/embed` URL.

## Restrictions

Embedded mode is only available for **fixed-amount** and **subscription** payment links. Product-based and flexible-amount links can't be embedded; opening their `/embed` URL emits a `PAYMENT_LINK_NOT_FOUND` iframe event and shows a "Link de pago no disponible" message.

## Embed snippet

Drop this snippet into your page, replacing the id with your payment link:

```html theme={null}
<div style="width: 100%; max-width: 600px; margin: 0 auto;">
  <iframe
    src="https://app.compago.com/checkout/pl/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/embed"
    width="100%"
    height="760"
    frameborder="0"
    allow="payment"
    style="border: none; border-radius: 8px;"
  ></iframe>
</div>
```

These dimensions are sized to comfortably contain the standard 3DS challenge window the buyer's bank may render during payment authorization. Smaller envelopes risk clipping the bank's verification dialog.

The embedded checkout never self-redirects. Your page decides what to render after a successful payment by listening for the [iframe events](#iframe-events) below.

## Customizing the button

The submit button inside the iframe can be customized via two **optional URL query parameters** on the embed URL. They override the default styling for that specific embed instance — they are not stored on the payment link.

| Query param   | Description                                                                                                                                             |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `buttonText`  | Custom text for the submit button. Falls back to the link's `callToActionMessage` when omitted.                                                         |
| `buttonColor` | Custom hex color (e.g. `#16A34A`). Applied to both background and border so the button reads as a single solid color. Falls back to black when omitted. |

```html theme={null}
<iframe
  src="https://app.compago.com/checkout/pl/{id}/embed?buttonText=Pay+Now&buttonColor=%2316A34A"
  width="100%"
  height="760"
  frameborder="0"
  allow="payment"
></iframe>
```

URL-encode the values: spaces become `+`, `#` becomes `%23`.

<Tip>
  The Compago dashboard's payment link detail page includes a built-in **embed snippet generator** that produces a ready-to-paste `<iframe>` with the correct URL — including any button customizations you preview in the dialog.
</Tip>

## Iframe events

The embedded checkout sends `postMessage` events to the parent window so you can react without a redirect.

### Events reference

| Event Type                     | Payload                                                          | When fired                                                  |
| ------------------------------ | ---------------------------------------------------------------- | ----------------------------------------------------------- |
| `COMPAGO_PAYMENT_LINK_LOADING` | `{ type }`                                                       | Checkout iframe loads                                       |
| `COMPAGO_PAYMENT_LINK_SUCCESS` | `{ type, data: { paymentId, paymentLinkId, amount, currency } }` | Payment completed successfully                              |
| `COMPAGO_PAYMENT_LINK_ERROR`   | `{ type, error: { code, message } }`                             | Payment failed, link disabled, deleted, or unsupported type |

### Listening for events

<CodeGroup>
  ```javascript JavaScript theme={null}
  window.addEventListener('message', (event) => {
    // Filter to only Compago payment link events
    if (!event.data?.type?.startsWith('COMPAGO_PAYMENT_LINK_')) {
      return;
    }

    switch (event.data.type) {
      case 'COMPAGO_PAYMENT_LINK_LOADING':
        console.log('Checkout is loading...');
        break;

      case 'COMPAGO_PAYMENT_LINK_SUCCESS':
        console.log('Payment completed!', event.data.data);
        // { paymentId, paymentLinkId, amount, currency }
        const { paymentId, paymentLinkId } = event.data.data;
        // Verify via your backend, then fulfill the order
        verifyAndFulfill(paymentId, paymentLinkId);
        break;

      case 'COMPAGO_PAYMENT_LINK_ERROR':
        console.error('Checkout error:', event.data.error);
        // { code, message }
        showErrorMessage(event.data.error.message);
        break;
    }
  });
  ```

  ```typescript TypeScript theme={null}
  type CompagoPaymentLinkEvent =
    | { type: 'COMPAGO_PAYMENT_LINK_LOADING' }
    | {
        type: 'COMPAGO_PAYMENT_LINK_SUCCESS';
        data: {
          paymentId: string;
          paymentLinkId: string;
          amount: number;
          currency: string;
        };
      }
    | {
        type: 'COMPAGO_PAYMENT_LINK_ERROR';
        error: { code: string; message: string };
      };

  window.addEventListener('message', (event: MessageEvent) => {
    const data = event.data as CompagoPaymentLinkEvent;

    if (!data?.type?.startsWith('COMPAGO_PAYMENT_LINK_')) {
      return;
    }

    switch (data.type) {
      case 'COMPAGO_PAYMENT_LINK_LOADING':
        console.log('Checkout is loading...');
        break;

      case 'COMPAGO_PAYMENT_LINK_SUCCESS':
        verifyAndFulfill(data.data.paymentId, data.data.paymentLinkId);
        break;

      case 'COMPAGO_PAYMENT_LINK_ERROR':
        showErrorMessage(data.error.message);
        break;
    }
  });
  ```
</CodeGroup>

### Error codes

The `error.code` field on `COMPAGO_PAYMENT_LINK_ERROR` events lets you distinguish failure modes:

| Code                                         | When                                                   |
| -------------------------------------------- | ------------------------------------------------------ |
| `PAYMENT_LINK_NOT_FOUND`                     | Unknown id, deleted, or unsupported type for embedding |
| `PAYMENT_LINK_INACTIVE`                      | Link is disabled (`status = INACTIVE`)                 |
| `PAYMENT_PLAN_NOT_APPLICABLE`                | Fee/plan lookup failed for the organization            |
| `INVALID_PAYMENT_AMOUNT`                     | Amount validation failed                               |
| `UNSUPPORTED_CARD_BRAND`                     | Customer used a non-VISA/Mastercard                    |
| `CARD_EXPIRED`                               | Customer's card is expired                             |
| `CARD_EXPIRED_BEFORE_LAST_INSTALLMENT`       | Card expires before the last MSI installment           |
| `DEBIT_CARD_NOT_ALLOWED_FOR_INSTALLMENTS`    | Debit card on an MSI plan                              |
| `INSTALLMENTS_NOT_ALLOWED_FOR_SUBSCRIPTIONS` | MSI requested on a subscription                        |
| `THREEDS_AUTH_FAILED`                        | 3DS challenge failed or was aborted                    |
| `PAYMENT_PROCESSING_FAILED`                  | Bank-level decline or processor error                  |
| `PAYMENT_TIMEOUT`                            | Step-up / processor timeout                            |
| `UNEXPECTED_ERROR`                           | Fallback for any other failure                         |

<Tip>
  Validate that `event.data.type` starts with `COMPAGO_PAYMENT_LINK_` before processing. This filters out unrelated `postMessage` events from other scripts or browser extensions.
</Tip>

<Warning>
  Always verify the payment status server-side before fulfilling the order. The `COMPAGO_PAYMENT_LINK_SUCCESS` event confirms the checkout completed in the iframe, but your backend should call your payment verification endpoint with the `paymentId` from the event payload to confirm the actual transaction state.
</Warning>

## Sizing the iframe

The embedded checkout uses a single-column responsive layout. Recommended starting dimensions:

* **Width**: 100% of a max \~600 px container.
* **Height**: 760 px (covers both fixed-amount and subscription links).

These dimensions are sized to comfortably contain the standard 3DS challenge window the buyer's bank may render during payment authorization. Smaller envelopes risk clipping the bank's verification dialog. Adjust with your own page's design language — the iframe content remains scrollable internally if it exceeds the height you set, but the 3DS challenge does not scroll gracefully on narrow viewports.

## Best practices

<AccordionGroup>
  <Accordion title="Always verify the payment server-side">
    The `COMPAGO_PAYMENT_LINK_SUCCESS` event confirms the iframe checkout completed, but a malicious actor could spoof a postMessage. Send the `paymentId` to your backend and verify with the Compago API before fulfilling.
  </Accordion>

  <Accordion title="Filter events by prefix">
    Other scripts and browser extensions also emit `postMessage` events. Always check `event.data.type.startsWith('COMPAGO_PAYMENT_LINK_')` before handling.
  </Accordion>

  <Accordion title="Set an explicit allow attribute">
    Include `allow="payment"` on the `<iframe>` so 3DS challenges and the Payment Request API work correctly inside the embed.
  </Accordion>

  <Accordion title="Don't expose dev-only customizations to end customers">
    The `buttonText` and `buttonColor` query params are intended for developer-controlled embeds. Don't accept them as user-supplied input — that would let third parties rewrite the button copy on your checkout.
  </Accordion>
</AccordionGroup>
