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
| Callback | Arguments | Description |
|---|---|---|
onReady | (none) | Widget iframe loaded and ready |
onStep | step, name | User navigated to a form step |
onQuoteSubmitted | (none) | Quote request sent to carriers |
onQuoteResults | count | Quotes received (may fire multiple times) |
onNoQuotes | (none) | No carriers returned quotes |
onError | message | Error during quoting |
onUserRegistered | email | User account created or linked |
onResize | height | Content height changed (pixels) |
onMessage | data | Catch-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 type | Payload | Description |
|---|---|---|
| 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:
- Event source: Check that
event.sourcematches your widget iframe'scontentWindow. - Event type prefix: Only process events where
data.typestarts 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');
},
});