Skip to main content

Embed Brightcall Dialer in iframe

Put the webphone inside your own web app through an iframe and drive it with postMessage events. Desktop browsers only.

1. Overview

The Brightcall Webphone can be embedded into any web application using a secure <iframe>, enabling outbound and inbound calling directly from your product without requiring users to switch context.

The integration is based on the standard browser postMessage API and provides a bidirectional communication channel between the host application and the Brightcall webphone. This allows the host application to:

  • Initiate outbound calls

  • Control agent authentication (login / logout)

  • Update contact metadata (e.g., display name)

  • Receive real-time call lifecycle events

  • Track agent session state

  • React to call status changes and call completion

The iframe operates as an isolated application while exposing a well-defined messaging interface for orchestration and event handling.

Key capabilities

  • Embedded softphone UI
    Fully functional webphone rendered inside your application.

  • Outbound call control
    Programmatically trigger calls with phone number and contact context.

  • Inbound call support
    Optional ability to receive incoming calls when the iframe remains loaded.

  • Real-time event streaming
    Call progress, connection state, completion, and summary events are broadcast to the host.

  • Agent session management
    Support for programmatic login and logout using secure agent hashes.

This document describes:

  • iFrame setup and configuration

  • URL parameters

  • Incoming messages (from Brightcall to host application)

  • Outgoing messages (from host application to Brightcall)

  • Security requirements

  • Recommended lifecycle

  • A complete working HTML example

Supported browsers

The integration relies on:

  • iframe embedding

  • window.postMessage

  • WebRTC audio APIs

It is therefore supported in all modern browsers, including:

  • Chrome

  • Edge

  • Firefox

  • Safari (latest versions)

Mobile browsers are not officially supported unless explicitly tested.

2. iFrame Setup

Before signing in, select Webphone (Voximplant) as the agent’s active device.

​

2.1 Example Markup

<iframe
id="webphone"
allow="microphone; autoplay; local-network-access"
src="https://app.brightcall.ai/webphone/iframe?isReceiveCalls=true&origin=https://example.com"
width="370"
height="520">
</iframe>
​

2.2 Required Attributes

Attribute

Description

allow

Must include microphone and autoplay

src

Webphone URL with query parameters

width

Recommended: 370

height

Recommended: 520

2.3 Allowed Permissions

microphone; autoplay; local-network-access

3. Webphone URL & Query Parameters

3.1 Base URLs

Environment

URL

Production

https://app.brightcall.ai/webphone/iframe


3.2 Supported Query Parameters

Parameter

Required

Description

isReceiveCalls

No

If true, the webphone can receive incoming calls (iframe must remain loaded)

origin

Yes

Exact origin of the parent page (scheme, host and port). Used to send messages back to the parent.

hash

No

Agent authentication hash for automatic login

Example

https://app.brightcall.ai/webphone/iframe?isReceiveCalls=true&origin=https://example.com&hash=AGENT_HASH

4. Receiving Messages from Brightcall (iframe → parent)

4.1 Transport Mechanism

Messages are delivered via:

window.addEventListener('message', handler); 

4.2 Mandatory Security Validation

Validate both the message origin and the sending iframe:

const iframe = document.getElementById('webphone');
function handler(event) {
if (event.origin !== 'https://app.brightcall.ai' ||
event.source !== iframe.contentWindow) return;
const message = event.data;
if (!message || typeof message !== 'object') return;
// Handle the message here.
}

Failure to validate origin introduces severe security risks.


4.3 Message Structure

type BroadcastMessage = {
type: BroadcastMessageType;
callData: WebphoneCallInfo | null;
agent?: IpmaxiDialerAppLoginResponseDto;
broadcastId?: number;
};

4.4 Message Fields

type (required)

Common event identifiers delivered to the parent:

enum BroadcastMessageType {
READY = 'ready',
CALL_STATUS_UPDATE = 'call_status_update',
LOG_IN = 'log_in',
LOG_OUT = 'log_out',
CALL_STARTED = 'call_started',
END_CALL = 'end_call',
SHOW_SUMMARY = 'show_summary'
}

callData: WebphoneCallInfo | null

May contain call data for READY and the following events; always check for null:

  • CALL_STATUS_UPDATE

  • CALL_STARTED

  • END_CALL

Contains:

  • Call status (Connecting, Answered, Completed, etc.)

  • Duration

  • Direction (incoming / outgoing)

  • Phone number

  • Call outcome & feedback, when available

SHOW_SUMMARY carries callData: null; use the latest call status or final call data for call details.


agent?: IpmaxiDialerAppLoginResponseDto

Present for:

  • LOG_IN

Contains:

  • Agent name

  • Extension

  • SIP info

  • Devices

  • Internal identifiers


broadcastId?: number

Optional identifier of the webphone instance.


4.5 Message Type Behaviour Reference

Type

When Sent

Expected Host Action

READY

iFrame initialized; agent login and telephony connection are separate

Mark integration as ready

LOG_IN

Agent login information is available

Store agent info

LOG_OUT

Agent logged out

Clear session

CALL_STARTED

Call attempt starts (Connecting)

Update call UI

CALL_STATUS_UPDATE

Status change, including Answered

Refresh call state; detect an answer from callData.callStatus

END_CALL

Call finalized, normally after the summary step

Finalize call

SHOW_SUMMARY

Post-call summary form is shown

Use the latest call status for call details

5. Sending Messages to Brightcall (parent → iframe)

5.1 Transport

const iframe = document.getElementById('webphone'); iframe.contentWindow.postMessage(message, IFRAME_ORIGIN);


5.2 Security Requirement

Always use the exact iframe origin:

const IFRAME_ORIGIN = 'https://app.brightcall.ai'; 

Never use "*" in production.


6. Supported Outgoing Message Types


6.1 Start Outbound Call

Type

type: "dial_number" 

Payload

{   type: "dial_number";   phone: string;   name?: string; } 

Example

iframe.contentWindow.postMessage( { type: 'dial_number', phone: '+14155552671', name: 'John Doe' }, IFRAME_ORIGIN );


6.2 Update Contact / Display Name

Updates the display name of an existing call. To set the initial name, include name in dial_number.

Type

type: "update_name" 

Payload

{   type: "update_name";   name: string; } 

6.3 Log Agent In

Type

type: "log_in" 

Payload

{   type: "log_in";   hash: AgentHash; } 

6.4 Log Agent Out

Type

type: "log_out" 

Payload

{   type: "log_out"; } 

7. Recommended Lifecycle

  1. Load iframe

  2. Wait for READY message

  3. Send log_in and wait for the webphone to connect

  4. Send dial_number, including name if needed

  5. Optionally send update_name during the call

  6. Track call using broadcast messages

  7. Send log_out when session ends


8. Error Handling

  • Invalid messages are silently ignored by the iframe

  • No error responses are guaranteed

  • Use broadcast messages (CALL_STATUS_UPDATE, END_CALL, etc.) as the source of truth

HTML example

Replace https://example.com in the origin parameter with the actual origin of your parent page.

<html lang="en">
<head>
<meta charset="UTF-8">
<title>Webphone Demo</title>
</head>
<body>
<table>
<tr>
<td>
<button onclick="startCall()">Start Call</button>
</td>
<td>
<input id="phone" placeholder="phone number">
</td>
</tr>
<tr>
<td>
<button onclick="updateName()">Update Name</button>
</td>
<td>
<input id="name" placeholder="name">
</td>
</tr>
<tr>
<td>
<button onclick="logIn()">Log In</button>
</td>
<td>
<input id="hash" placeholder="hash">
</td>
</tr>
<tr>
<td>
<button onclick="logOut()">Log out</button>
</td>
<td></td>
</tr>
</table>
<table>
<tr>
<td> Webphone status:
</td>
<td>
<div id="webphone_status">not ready</div>
</td>
</tr>
<tr>
<td> Login status:
</td>
<td>
<div id="login_status"></div>
</td>
</tr>
<tr>
<td> Call Info:
</td>
<td>
<div id="call_info"></div>
</td>
</tr>
<tr>
<td> Call status:
</td>
<td>
<div id="call_status"></div>
</td>
</tr>
</table> <br><br>
<iframe id="webphone" allow="microphone; autoplay; local-network-access" src="https://app.brightcall.ai/webphone/iframe?isReceiveCalls=true&origin=https://example.com" width="370" height="520">
</iframe> <script>
const IFRAME_ORIGIN = 'https://app.brightcall.ai';
const iframe = document.getElementById('webphone');
function startCall() {
const phone = document.getElementById('phone').value;
const name = document.getElementById('name').value;
if (phone) {
iframe.contentWindow.postMessage(
{ type: 'dial_number', phone, name }, IFRAME_ORIGIN
);
}
}
function updateName() {
const name = document.getElementById('name').value;
if (name) {
iframe.contentWindow.postMessage(
{ type: 'update_name', name }, IFRAME_ORIGIN
);
}
}
function logIn() {
const hash = document.getElementById('hash').value;
if (hash) {
iframe.contentWindow.postMessage(
{ type: 'log_in', hash }, IFRAME_ORIGIN
);
}
}
function logOut() {
iframe.contentWindow.postMessage(
{ type: 'log_out' }, IFRAME_ORIGIN
);
}
window.addEventListener('message', (event) => {
if (event.origin !== IFRAME_ORIGIN ||
event.source !== iframe.contentWindow) return;
const data = event.data;
if (!data || typeof data !== 'object') return;
switch (data.type) {
case 'ready':
document.getElementById('webphone_status').textContent = 'ready';
break;
case 'log_in':
if (data.agent) {
document.getElementById('login_status').textContent =
`Logged in as ${data.agent.name}`;
}
break;
case 'log_out':
document.getElementById('login_status').textContent = 'Logged out';
break;
case 'call_started':
if (data.callData) {
document.getElementById('call_info').textContent =
`New call ${data.callData.isIncoming ? 'from' : 'to'} ${data.callData.callerDisplayName ?? data.callData.phoneNumber}`;
}
break;
case 'end_call':
document.getElementById('call_info').textContent =
`Call ended. ${data.callData?.outcome ? 'Call outcome: ' + data.callData.outcome.outcome : ''}`;
break;
case 'call_status_update':
if (data.callData) {
document.getElementById('call_status').textContent =
data.callData.callStatus;
}
break;
}
});
</script>
</body>
</html>

Did this answer your question?