Paytia embedded iframe events
Paytia’s web-checkout flow can return a checkout page as an iframe URL instead of redirecting the customer. While the customer pays inside that iframe, Paytia sends browser postMessage events to your host page so you can resize the iframe, react to payment outcome, and track which payment method was selected.
This is separate from webhooks. Webhooks are server-to-server HTTP POSTs to webhook_url for status updates and webhook_url_transaction for transaction outcomes. Iframe events are browser-only and go to whatever you declare in iframeevent_url.
Sources in this repo
POST multipart form data to:
https://accounts.paytia.com/api/payment_service (production)
Key fields for iframe mode:
Paytia responds with a checkout_url (sometimes a bare URL, sometimes HTML containing an iframe src). Your host page embeds it:
<iframe id="payment_iframe"
src="https://accounts.pay729.guru:449/…/checkout/…"
style="width:100%; height:700px; border:1px solid #ccc"
referrerpolicy="strict-origin-when-cross-origin"></iframe>
The iframe is cross-origin (Paytia domain). You cannot read its DOM or style its internals. Communication is only via postMessage.
On the parent window (your merchant site, or the QA console API Tester tab):
window.addEventListener("message", function (event) {
const allowedOrigins = [
"https://accounts.pay729.guru:449",
"https://accounts.paytia.com",
"https://accounts.payt729.net",
# check the domain you signed up your account with
];
if (!allowedOrigins.includes(event.origin)) return;
const data = event.data;
if (!data || typeof data !== "object") return;
if (data.type === "resize-iframe") {
const iframe = document.getElementById("payment_iframe");
if (iframe && typeof data.iframeHeight === "number") {
// Fractional heights (e.g. 242.97) need Math.ceil or you get a 1px scrollbar
iframe.style.height = Math.ceil(data.iframeHeight) + "px";
}
}
if (data.type === "payment-status") {
if (data.status === "success") { /* show thank-you, redirect, etc. */ }
if (data.status === "failed") { /* show decline message */ }
if (data.status === "error") { /* validation / system error */ }
}
if (data.type === "payment-method") {
console.log("Customer selected:", data.payment_type);
// card | google | paypal | bank | apple | "" (empty string)
}
});
Direction: Paytia checkout iframe (child) → window.parent.postMessage(...) → your listener.
Origin check: Always verify event.origin against Paytia’s checkout hosts. The QA console ships these defaults in GET /api/api-tester/capabilities → defaultIframeEventOrigins.
While the checkout loads, resizes, or completes, the iframe posts structured JSON objects. Three documented event types:
Sent when checkout content height changes (form steps, validation messages, wallet buttons, etc.).
Example (toggle between two heights in the QA self-test page):
{
"type": "resize-iframe",
"iframeHeight": 480
}
After the customer expands a section:
{
"type": "resize-iframe",
"iframeHeight": 760
}
Real-world detail: heights are often fractional (e.g. 242.97). The QA console rounds up with Math.ceil() so the iframe is not clipped by a sub-pixel scrollbar.
Host behaviour: set iframe.style.height from iframeHeight; width stays under your control (100%, 375px, etc.).
Sent when payment succeeds, fails, or errors. The status string drives branching:
Example — success (from relay-child.html simulator, matches fields the API Tester surfaces):
{
"type": "payment-status",
"status": "success",
"message": "Payment completed",
"payment_status": "paid",
"card_brand": "Visa",
"card_last_four_digit": "4242",
"gateway_response": "Approved",
"customer_message": "Thank you, your payment was successful",
"transaction_id": "TEST-1719408123456",
"amount": "10.00"
}
Example — failed:
{
"type": "payment-status",
"status": "failed",
"message": "Payment declined",
"payment_status": "declined",
"card_brand": "Mastercard",
"card_last_four_digit": "0002",
"gateway_response": "Do Not Honour",
"customer_message": "Your card was declined. Please try another card.",
"user_error_message": "Declined by issuer (51)",
"transaction_id": "TEST-1719408123456",
"amount": "10.00"
}
Fields commonly present on payment-status (API Tester summary table):
With payment_completionscreen=0, your host page should use this payload to show your own confirmation UI instead of Paytia’s built-in completion screen.
With payment_completionscreen=1 (default), Paytia keeps its completion screen inside the iframe; you may still receive the event for logging/analytics.
Sent when the customer picks or changes payment method (card vs Apple Pay vs Google Pay, etc.).
Example:
{
"type": "payment-method",
"payment_type": "card"
}
Documented payment_type values (from the generated listener template):
card · google · paypal · bank · apple · "" (empty string)
Useful for analytics (“customer opened Google Pay”) or adjusting host-page chrome before payment completes.
In local dev you typically:
Point webhooks at the WAN relay (PAYTIA_QA_WEBHOOK_BASE_URL)
Point iframeevent_url at http://localhost:8787 (where the API Tester tab is open)
dashboardOrigin drives the auto-fill — it is never derived from the webhook env var. See docs/CONSOLE-ENVIRONMENTS.md.
From the master spec (paytia-api.json):
URL that receives checkout-iframe events (resize-iframe / payment-status / payment-method). Must EXACTLY match the page hosting your listener, or no events are sent.
In the QA console, click “Set iframeevent_url to this page” — it sets the value to the current dashboardOrigin (e.g. http://localhost:8787, or https://<relay-host>/wan/<slug> when using the WAN dashboard).
If you paste the test page into W3Schools Tryit, set iframeevent_url to https://www.w3schools.com (that host) and add that origin to Allowed event origins.
The parent filters event.origin. Paytia’s checkout iframe sends from Paytia domains, not from your site. Default allow-list in the console:
https://accounts.paytia.com
https://accounts.pay729.net
Note: The subdomain is variable and can change based on the partner associated with your account.
Messages from devtools, Vite, or setImmediate polyfills are ignored as noise in the API Tester.
sequenceDiagram
participant Host as Your host page
participant PaytiaAPI as Paytia payment_service
participant Iframe as Paytia checkout iframe
Host->>PaytiaAPI: POST payment_service<br/>(is_embedded_url=1, iframeevent_url=Host, transaction_flag=8)
PaytiaAPI-->>Host: checkout_url
Host->>Iframe: embed checkout_url in #payment_iframe
Iframe->>Host: postMessage { type: resize-iframe, iframeHeight: 520 }
Host->>Host: iframe.style.height = 520px
Iframe->>Host: postMessage { type: payment-method, payment_type: card }
Note over Host: Customer enters card details
Iframe->>Host: postMessage { type: payment-status, status: success, ... }
Note over Host: Show your completion UI if payment_completionscreen=0
PaytiaAPI->>Host: POST webhook_url (server-side, separate channel)