by InlinexDev

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.

iframepostMessageShopifycross-originJavaScript

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:

  1. The sender specifies the target origin — prevents messages from going to unintended recipients
  2. The receiver validates the origin — prevents processing messages from malicious senders
  3. 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.log in both the iframe and parent to trace message flow
  • Check the origin in the console — mismatches are the most common issue
  • Test with both http and https — 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 System

A full-featured appointment booking system with WhatsApp OTP verification, intelligent slot management, and automated reminders for a premium inline skate retail store in Singapore.