Embed the Checkout
Load the checkout in an iframe on your page and follow the payment with postMessage events.
Every session has a checkout url. You can redirect the customer to it, or embed it in an
iframe on your own page and receive events as the customer moves through the payment.
| Hosted (redirect) | Embedded (iframe) | |
|---|---|---|
checkout_type | hosted (default) | embedded |
| Where the customer pays | On Tonder's page | Inside your page |
| How you follow it | Redirect URLs + webhooks | postMessage events + webhooks |
Your site's domain must be enabled for embedding in each environment. Ask
Tonder support to add it before you test, and give them the origin
only — scheme and domain, for example https://shop.example.com, never a path. An entry that
includes a path never matches, and the iframe then stays blank on every page of your site without
any error in the network tab or the console.
Step by step
Create the session with checkout_type: "embedded" and post_message_enabled: true. Add
redirect_on_completion: "never" if you want your page, not the checkout, to decide where the
customer goes at the end.
{
"customer": { "first_name": "Ana", "last_name": "Garcia", "email": "ana@example.com" },
"amount_total": 100.00,
"currency": "MXN",
"line_items": [{ "name": "Deposit", "quantity": 1, "unit_price": 100.00 }],
"payment_method_types": ["card", "spei"],
"external_id": "ORD-001",
"return_url": "https://your-store.com/checkout/complete",
"checkout_type": "embedded",
"post_message_enabled": true,
"redirect_on_completion": "never"
}Accept messages only from the checkout origin of your environment:
https://stage-payflow.tonder.io (Sandbox) or https://payflow.tonder.io (Production).
Use the url from the session response as the iframe src.
Events tell your page what to show. Confirm the payment itself from your backend, with webhooks or Get a session.
Events
| Event | When | transaction_status | session_status |
|---|---|---|---|
checkout.initiated | The checkout has loaded | — | pending |
checkout.redirected | The customer goes to a voucher or external payment page | Pending | pending |
checkout.returned | The customer comes back from that page | varies | varies |
checkout.failed | An attempt failed; the customer can retry (may repeat) | Failed, Declined | pending |
checkout.completed | Payment or card saving succeeded (sent once) | Success | completed |
Event fields
| Field | Description |
|---|---|
event | Event name. |
session_type | payment or enrollment. |
payment_id | The payment (not present on enrollment). |
direct_transaction_id | Direct API accounts only: the latest attempt (not present before the first attempt). |
external_id | Your order reference. |
transaction_status, session_status | Current statuses. |
payment_method | Method used, e.g. card, oxxopay. |
{
"event": "checkout.completed",
"session_type": "payment",
"payment_id": 41528,
"external_id": "ORD-001",
"transaction_status": "Success",
"session_status": "completed",
"payment_method": "card"
}Example page
<!DOCTYPE html>
<html>
<body>
<div id="status"></div>
<script>
window.addEventListener('message', function (event) {
const allowedOrigins = ['https://payflow.tonder.io', 'https://stage-payflow.tonder.io'];
if (!allowedOrigins.includes(event.origin)) return;
const data = event.data;
switch (data.event) {
case 'checkout.initiated':
document.getElementById('status').textContent = 'Ready to pay';
break;
case 'checkout.completed':
window.location.href = data.session_type === 'enrollment'
? '/card-saved'
: '/success?payment=' + data.payment_id;
break;
case 'checkout.failed':
document.getElementById('status').textContent =
'Payment failed: ' + data.transaction_status + '. Please try again.';
break;
case 'checkout.redirected':
document.getElementById('status').textContent =
'Redirecting to ' + data.payment_method + '...';
break;
case 'checkout.returned':
if (data.transaction_status === 'Success') {
window.location.href = '/success?payment=' + data.payment_id;
}
break;
}
});
</script>
<iframe id="checkout-iframe" src="SESSION_URL" style="width: 100%; height: 600px; border: none;"></iframe>
</body>
</html>Never fulfill an order from a browser event alone. A message in the page can be forged; the webhook or a server-side read of the session is the source of truth.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| The iframe stays blank, with no error in the console or the network tab | Your domain is not enabled for embedding in this environment, or it was registered with a path | Ask Tonder support to enable the origin only (for example https://shop.example.com) in the environment you are testing |
| No events reach your page | The session was not created with checkout_type: "embedded" and post_message_enabled: true, or your listener checks a different origin | Recreate the session with both fields, and accept https://stage-payflow.tonder.io (Sandbox) or https://payflow.tonder.io (Production) |
