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.
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.
