Webhooks & Events

The widget communicates with the parent page via window.postMessage. All events are prefixed with bh: and include a type field.

Listening for events

There are two ways to receive events from the widget:

1. SDK callbacks (recommended)

Pass callback functions when initializing via the SDK. The SDK filters and dispatches events for you:

const widget = BHWidget.init({
  key: 'YOUR_KEY',
  type: 'auto',
  container: '#my-widget',
  onReady: () => {
    console.log('Widget loaded');
  },
  onStep: (step, name) => {
    console.log('Step', step, name);
  },
  onQuoteSubmitted: () => {
    console.log('Quotes requested');
  },
  onQuoteResults: (count) => {
    console.log('Received', count, 'quotes');
  },
  onNoQuotes: () => {
    console.log('No quotes available');
  },
  onError: (message) => {
    console.error('Error:', message);
  },
  onUserRegistered: (email) => {
    console.log('User registered:', email);
  },
  onResize: (height) => {
    console.log('Content height:', height);
  },
  onMessage: (data) => {
    console.log('Raw event:', data.type, data);
  },
  // Modal-only callbacks
  onOpen: () => {
    console.log('Modal opened');
  },
  onClose: () => {
    console.log('Modal closed');
  },
});

2. Raw postMessage listener

If using a direct iframe without the SDK, listen on the window:

window.addEventListener('message', (event) => {
  const data = event.data;

  // Only handle BenefitHub widget events
  if (!data || typeof data.type !== 'string' || !data.type.startsWith('bh:')) {
    return;
  }

  switch (data.type) {
    case 'bh:ready':
      console.log('Widget ready');
      break;
    case 'bh:step':
      console.log('Step', data.step, data.name);
      break;
    case 'bh:quote-submitted':
      console.log('Quote submitted');
      break;
    case 'bh:quote-results':
      console.log('Got', data.count, 'quotes');
      break;
    case 'bh:no-quotes':
      console.log('No quotes returned');
      break;
    case 'bh:error':
      console.error('Widget error:', data.message);
      break;
    case 'bh:user-registered':
      console.log('User:', data.email);
      break;
    case 'bh:resize':
      // Auto-resize the iframe
      document.querySelector('iframe').style.height = data.height + 'px';
      break;
  }
});

// Modal events are dispatched as CustomEvents (not postMessage)
window.addEventListener('bh:modal-open', () => {
  console.log('Modal opened');
});
window.addEventListener('bh:modal-close', () => {
  console.log('Modal closed');
});

SDK callback reference

CallbackArgumentsDescription
onReady(none)Widget iframe loaded and ready
onStepstep, nameUser navigated to a form step
onQuoteSubmitted(none)Quote request sent to carriers
onQuoteResultscountQuotes received (may fire multiple times)
onNoQuotes(none)No carriers returned quotes
onErrormessageError during quoting
onUserRegisteredemailUser account created or linked
onResizeheightContent height changed (pixels)
onMessagedataCatch-all: receives every bh:* event as raw data
onOpen(none)Modal opened (modal mode only)
onClose(none)Modal closed (modal mode only)

3. CustomEvent listeners

When the SDK is loaded, it also dispatches events as CustomEvents on window, so you can listen with standard DOM APIs:

window.addEventListener('bh:quote-results', (event) => {
  const { count } = event.detail;
  console.log('Got', count, 'quotes');
});

window.addEventListener('bh:error', (event) => {
  console.error('Widget error:', event.detail.message);
});

Event Reference

Event typePayloadDescription
bh:ready{}Fired when the widget iframe has loaded and is ready to accept user input. Use this to show/hide loading states.
bh:step{ step: number, name: string }Fired when the user navigates to a new form step. step is the 0-based index, name is the step label (e.g., "Address", "Vehicles", "Coverage").
bh:quote-submitted{}Fired when the user has submitted their information and the quote request has been sent to carriers. The widget transitions to a loading/search state.
bh:quote-results{ count: number }Fired when quote results are received. count is the total number of quotes returned. May fire multiple times as streaming results arrive.
bh:no-quotes{}Fired when no carrier returned a quote. This usually means the risk profile was declined by all carriers.
bh:error{ message: string }Fired when an error occurs during quoting. message is a human-readable error description.
bh:user-registered{ email: string }Fired after the user's BenefitHub account has been created or linked. Includes the user's email address.
bh:resize{ height: number }Fired when the widget content height changes. height is in pixels. The SDK automatically resizes the iframe; use this only if you need custom sizing logic.
bh:modal-open{}Modal mode only. Dispatched as a CustomEvent on window when the modal overlay opens. Also triggers the onOpen callback.
bh:modal-close{}Modal mode only. Dispatched as a CustomEvent on window when the modal overlay closes (via X button, backdrop click, or ESC key). Also triggers the onClose callback.
bh:request-close{}Modal mode only. Sent by the widget iframe to request the SDK close the modal. The SDK calls .close() automatically when this event is received.

Security considerations

When listening to postMessage events directly, always verify:

  1. Event source: Check that event.source matches your widget iframe's contentWindow.
  2. Event type prefix: Only process events where data.type starts with "bh:".
const iframe = document.querySelector('iframe');

window.addEventListener('message', (event) => {
  // Verify the message comes from our widget iframe
  if (event.source !== iframe.contentWindow) return;

  // Only handle BH events
  if (!event.data?.type?.startsWith('bh:')) return;

  // Safe to process
  handleWidgetEvent(event.data);
});

Analytics integration

Common pattern for tracking widget usage in Google Analytics, Segment, or similar:

const widget = BHWidget.init({
  key: 'YOUR_KEY',
  type: 'auto',
  container: '#widget',
  onStep: (step, name) => {
    gtag('event', 'insurance_step', {
      step_number: step,
      step_name: name,
    });
  },
  onQuoteSubmitted: () => {
    gtag('event', 'insurance_quote_submitted');
  },
  onQuoteResults: (count) => {
    gtag('event', 'insurance_quotes_received', {
      quote_count: count,
    });
  },
  onNoQuotes: () => {
    gtag('event', 'insurance_no_quotes');
  },
  onUserRegistered: (email) => {
    gtag('event', 'insurance_user_registered');
  },
});

Next steps