Cross-Origin Iframe Communication with postMessage: A Practical Guide
How to embed third-party widgets in Shopify and other platforms using iframes with secure cross-origin communication via postMessage.
The Iframe Challenge
When you build a web application that needs to be embedded in another site — a booking widget on Shopify, a review badge on WordPress, a chat widget on any platform — iframes are often the only option. But cross-origin restrictions make communication between the iframe and parent page tricky.
Why Iframes?
For the Inlinex booking system, the widget needed to:
- Run on a different domain than the Shopify store
- Have its own styling without conflicting with the theme
- Communicate height changes to the parent for seamless layout
- Pass booking data back to the parent page
The postMessage API
window.postMessage is the standard way for cross-origin windows to communicate. The key principles:
- The sender specifies the target origin — prevents messages from going to unintended recipients
- The receiver validates the origin — prevents processing messages from malicious senders
- Data is serialized — you can send objects, arrays, and primitives
Sending from Iframe to Parent
// Inside the iframe (booking widget)
function notifyParent(type, data) {
window.parent.postMessage(
{ source: 'booking-widget', type, data },
'*' // In production, specify the exact parent origin
);
}
// Send height updates
function updateHeight() {
const height = document.documentElement.scrollHeight;
notifyParent('resize', { height });
}
// Send booking confirmation
function onBookingComplete(booking) {
notifyParent('booking-complete', {
id: booking.id,
date: booking.date,
time: booking.time
});
}
Receiving in Parent Page
// On the Shopify storefront
window.addEventListener('message', (event) => {
// Validate origin
if (event.origin !== 'https://booking.inlinex.sg') return;
const { source, type, data } = event.data;
if (source !== 'booking-widget') return;
switch (type) {
case 'resize':
const iframe = document.getElementById('booking-iframe');
iframe.style.height = `${data.height}px`;
break;
case 'booking-complete':
showConfirmation(data);
break;
}
});
Auto-Resizing Iframe
The most common iframe challenge is height. Fixed heights either clip content or leave empty space. The solution is dynamic resizing:
// In the iframe - observe content changes
const resizeObserver = new ResizeObserver(() => {
const height = document.documentElement.scrollHeight;
window.parent.postMessage(
{ source: 'booking-widget', type: 'resize', data: { height } },
'*'
);
});
resizeObserver.observe(document.body);
Parent-Side Embed Script
Provide merchants with a simple embed snippet:
<div id="booking-widget-container">
<iframe
id="booking-iframe"
src="https://booking.inlinex.sg/widget?store=example"
style="width: 100%; border: none; overflow: hidden;"
scrolling="no"
></iframe>
</div>
<script>
window.addEventListener('message', function(e) {
if (e.data.source !== 'booking-widget') return;
if (e.data.type === 'resize') {
document.getElementById('booking-iframe').style.height =
e.data.data.height + 'px';
}
});
</script>
Security Best Practices
1. Always Validate Origin
const ALLOWED_ORIGINS = [
'https://mystore.myshopify.com',
'https://mystore.com'
];
window.addEventListener('message', (event) => {
if (!ALLOWED_ORIGINS.includes(event.origin)) {
console.warn(`Rejected message from ${event.origin}`);
return;
}
// Process message
});
2. Use a Message Protocol
Structure messages with a consistent schema:
const MessageSchema = {
source: 'string', // Identifies the sender
type: 'string', // Message type
data: 'object', // Payload
version: 'number' // Protocol version for backward compatibility
};
3. Sanitize Data
Never trust data from postMessage without validation:
function handleResize(data) {
const height = parseInt(data.height, 10);
if (isNaN(height) || height < 0 || height > 10000) {
return; // Invalid height
}
iframe.style.height = `${height}px`;
}
Shopify-Specific Considerations
- Theme compatibility — test your iframe across popular Shopify themes (Dawn, Sense, Craft)
- Mobile responsiveness — iframes must adapt to Shopify's mobile layout
- Loading performance — lazy-load the iframe below the fold
- Shopify Liquid — inject the embed code via a Liquid snippet for easier installation
{% comment %} snippets/booking-widget.liquid {% endcomment %}
<div id="booking-widget-container" style="max-width: 600px; margin: 0 auto;">
<iframe
src="https://booking.inlinex.sg/widget?store={{ shop.permanent_domain }}"
style="width: 100%; border: none;"
loading="lazy"
title="Book an appointment"
></iframe>
</div>
Debugging Tips
- Use browser DevTools Console to monitor postMessage events
- Add
console.login both the iframe and parent to trace message flow - Check the origin in the console — mismatches are the most common issue
- Test with both
httpandhttps— origins include the protocol
Conclusion
postMessage is the secure, standardized way to build embedded widgets. With proper origin validation, structured message protocols, and auto-resizing, you can create seamless embedded experiences that work across any host platform.
Related Project
Inlinex Booking SystemA full-featured appointment booking system with WhatsApp OTP verification, intelligent slot management, and automated reminders for a premium inline skate retail store in Singapore.