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.
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://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-scriptsis required for checkout interactivity, API calls, timers, copy buttons, and live status updates. - —
allow-same-originlets the Paymos-hosted checkout run with its own origin instead of an opaque sandbox origin. - —
allow-formsallows normal form interactions inside checkout. - —
allow-top-navigationis omitted so the iframe cannot navigate the merchant page. - —
allow-popupsis omitted because the checkout does not need to open extra browser windows.
When to use Widget SDK instead
Use Widget 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.