Skip to content

Integrations

On this page

Hosted Checkout

Redirect customers to Hosted Checkout or embed the payment page in a restricted iframe, then fulfil orders from the signed confirmation webhook.

Use the payment_url returned by POST /v1/invoices as the canonical checkout URL. The same URL supports two presentation modes — full-page redirect and embedded iframe.

Both modes work on any project except a Telegram-bot one. There the same payment_url is a deep link into the Paymos bot — no hosted page to redirect to or embed.

Redirect mode

Redirect mode is the simplest integration. Send the customer to payment_url after your server creates the invoice:

<a href="https://checkout.paymos.io/invoice/inv_xxx">Pay invoice</a>

The customer completes the payment on the Paymos-hosted page. Your server fulfills the order only after receiving and verifying the signed webhook event.

Embed mode

Embed mode uses the same payment_url with ?embed=true. Use this when your server already creates invoices and you only want the hosted checkout UI inside your own page:

<iframe
  src="https://checkout.paymos.io/invoice/inv_xxx?embed=true"
  title="Paymos checkout"
  allow="clipboard-write"
  sandbox="allow-scripts allow-same-origin allow-forms"
  style="display:block;width:100%;max-width:460px;height:min(90vh,900px);border:0;"
></iframe>

Embed events

The checkout iframe sends postMessage transport events to the parent page:

Event Meaning
paymos:succeeded Checkout reached paid or paid_over.
paymos:failed Checkout reached a terminal failure state such as underpaid, expired, or cancelled.
paymos:close Customer clicked a close/back action inside the embedded checkout.

Parent pages must validate both the sender origin and the iframe source:

const iframe = document.querySelector('#paymos-checkout');

window.addEventListener('message', (event) => {
  if (event.origin !== 'https://checkout.paymos.io') return;
  if (event.source !== iframe.contentWindow) return;
  if (!event.data || typeof event.data !== 'object') return;

  switch (event.data.type) {
    case 'paymos:succeeded':
      console.log('Payment UI succeeded:', event.data.invoice_id);
      break;
    case 'paymos:failed':
      console.warn('Payment UI failed:', event.data.reason);
      break;
    case 'paymos:close':
      iframe.remove();
      break;
  }
});

Treat these events as UX hints, not proof of payment. Drive spinners, redirects, and analytics from them — but never ship goods, credit balances, or mark an order paid based on a postMessage. A postMessage is trivial to spoof: any page sharing the customer's origin can post a fake paymos:succeeded. Settlement is confirmed only by the signed server-side webhook — the one source of truth for fulfillment.

Security model

The recommended iframe sandbox is deliberately small:

  • allow-scripts is required for checkout interactivity, API calls, timers, copy buttons, and live status updates.
  • allow-same-origin lets the Paymos-hosted checkout run with its own origin instead of an opaque sandbox origin.
  • allow-forms allows normal form interactions inside checkout.
  • allow-top-navigation is omitted so the iframe cannot navigate the merchant page.
  • allow-popups is omitted because the checkout does not need to open extra browser windows.

When to use Low-Code SDK instead

Use Low-Code SDK when you want Paymos to create invoices from browser-side config, render the payment button, manage the modal lifecycle, validate iframe messages, and dispatch paymos:* CustomEvents for you.

Use manual iframe embed when your backend already owns invoice creation and you only need to place the hosted checkout UI inside your page.