The JavaScript API lets your own page drive a running meeting room: mute your own microphone, read the participants list, send chat messages, change devices, and listen for events. It works whether you embed the room as an iFrame or use the Veeting Blocks web components.
If you are using an AI coding assistant, copy the text below into it and add a sentence describing what you want to build. AI assistants accessing this page receive a separate, token-optimized version of our full developer documentation, specifically tailored by our engineers for AI coding assistants.
Before writing any code, read this page:
https://www.veeting.com/en/developer-documentation/javascript-apis
It documents the JavaScript API of a Veeting Rooms meeting room.
Keep these five rules in mind:
1. Ask me first where my code runs: INSIDE the room in Veeting
Blocks, in a custom tool, or in the page around an embedded
iFrame. Each reaches the API differently and the answer changes
everything you write. A custom tool gets no handler and no
events: it can only post {action, payload} to window.parent.
2. Wait for the "joined" event before calling methods that need the
participant to be in the meeting.
3. "connected" means the websocket to our API is up. It says nothing
about WebRTC media. "disconnected" fires when that websocket drops
and also when the media connection drops.
4. Only use methods listed on that page. Do not invent them and do not
guess a signature.
5. From a parent page you talk to the room with postMessage:
{action, payload} in, {event, payload} out. It is one-way, so no
method that returns a value is usable there. Check message.origin
on everything you receive.
What I want to build:Where your code runs matters more than anything else on this page. Settle it first.
| Your code runs | How you reach the API |
|---|---|
| Inside the meeting room: Veeting Blocks | Directly, through the handler. You get the full API, including return values. |
| In a custom tool (an iFrame inside the room) | Only by posting { action, payload } to window.parent. No events and no return values. |
| In the page around an embedded iFrame | Through postMessage. You can call any method that returns nothing, and you receive every event. |
If you embed the room in an iFrame, your page and the meeting room are two different documents, usually on two different origins. Your page cannot reach into the room's window, and the room cannot reach into yours. Everything crosses that boundary as a message.
Cross-origin is expected and supported. The room normally runs on your own white-label domain, such as meeting.example.com, while your application sits on app.example.com or somewhere else entirely. Neither case needs anything special: a different subdomain and a completely different domain behave the same way here.
The meeting room posts every event to its parent window. Listen for those messages:
window.addEventListener("message", (message) => {
// Always check the origin. The room posts to any parent, so this check is
// yours to make, and without it any page could forge these events.
if (message.origin !== "https://<DOMAIN-NAME>") {
return;
}
const { event, payload } = message.data;
if (event === "joined") {
console.log("The meeting room is ready");
}
if (event === "participantsUpdated") {
console.log(`${payload.length} participants in the room`);
}
});The event is one of the thirteen names below, and payload is exactly what that event carries. You do not have to subscribe: every event is posted to the parent whether you listen or not.
Post a message to the iFrame that names the method and its arguments:
const room = document.getElementById("meeting-frame");
room.contentWindow.postMessage({
action: "muteAudio",
payload: [true]
}, "https://<DOMAIN-NAME>");action is the method name, and payload holds its arguments in order, as an array. For a method that takes no arguments, pass payload: [] or omit payload entirely.
Because messaging across the boundary is one-way, two limits apply:
get is therefore useless across the boundary. Read the participants from the participantsUpdated event instead of calling getParticipantsList(), and take the room configuration from meetingRoomConfigUpdated.A mute button and a participant counter in your own page, outside the iFrame:
<button id="mute">Mute</button>
<span id="count">0</span> participants
<iframe id="meeting-frame"
src="https://<DOMAIN-NAME>/meeting/<MEETING-ID>"
allow="microphone;camera;encrypted-media;fullscreen;autoplay;display-capture;layout-animations;">
</iframe>
<script>
const ROOM_ORIGIN = "https://<DOMAIN-NAME>";
const room = document.getElementById("meeting-frame");
let muted = false;
window.addEventListener("message", (message) => {
if (message.origin !== ROOM_ORIGIN) {
return;
}
if (message.data.event === "participantsUpdated") {
document.getElementById("count").textContent = message.data.payload.length;
}
});
document.getElementById("mute").addEventListener("click", () => {
muted = !muted;
room.contentWindow.postMessage({ action: "muteAudio", payload: [muted] }, ROOM_ORIGIN);
});
</script>This section applies to Veeting Blocks code running inside the meeting room. A page that embeds an iFrame uses the messages above instead. A custom tool gets no handler: it can only post { action, payload } to window.parent.
The API only exists once the meeting room has finished loading, so you cannot call it from a script that runs at page load. Instead, define a wlvmrApiReady function on window. The meeting room calls that function when it is ready and passes the name of the object it registered itself under.
window["wlvmrApiReady"] = (handler) => {
if (!window[handler]) {
console.error(`Handler window.${handler} not found!`);
return;
}
// Every API method lives on this object
window[handler].muteAudio(true);
};Two rules follow, and both trip people up:
wlvmrApiReady before the meeting room loads. If you assign it afterward, the call has already happened and you never receive the handler.window[handler], never a literal.Waiting for the room, subscribing to events, then calling methods:
window["wlvmrApiReady"] = (handler) => {
if (!window[handler]) {
console.error(`Handler window.${handler} not found!`);
return;
}
const api = window[handler];
api.on("joined", () => {
console.log("The meeting room is ready to use");
});
api.on("participantsUpdated", (participants) => {
console.log(`${participants.length} participants in the room`);
});
api.on("chatMessage", (message) => {
console.log(`${message.fromParticipantName}: ${message.message}`);
});
api.setAudioInputDeviceId("default", false);
api.setVideoInputDeviceId("default", true);
};Subscribe with on(event, callback). There are thirteen events.
| Event | Fires when | Payload |
|---|---|---|
beforeConnecting | Just before the room connects its media | none |
connected | The websocket to the Veeting API is established and in use | none |
joined | The meeting room is ready to use | none |
disconnected | The API websocket or the media connection drops | none |
leave | The local participant leaves the meeting | none |
participantsUpdated | Somebody joins, leaves, or changes state | IApiParticipant[] |
chatMessage | A group chat message arrives | IApiChatMessage |
privateChatMessage | A private chat message arrives | IApiChatMessage |
chatModeration | A chat message's moderation state changes | IApiChatModeration |
meetingDurationUpdated | The remaining meeting time changes | IApiRemainingTimeUpdate |
meetingRoomConfigUpdated | The room configuration changes | IMeetingRoomConfig |
customMessage | Another participant sends a custom message | ICustomMessage |
screenshareStateChange | Screen sharing starts or stops | IScreenshareState |
Note:
connecteddescribes only the websocket to our API, not the WebRTC media.disconnectedfires when that websocket drops and also when the media connection drops, so the event alone does not tell you which one went down.
| Method | Description |
|---|---|
getVersion(): string | Returns the version string of the running meeting room. |
getParticipantsList(): IApiParticipant[] | Returns the current participants list. |
leaveMeeting(): void | Disconnects from the server and leaves the meeting room. |
enableFollowMe(enabled: boolean): void | Enables or disables Follow Me. Requires moderator rights. |
| Method | Description |
|---|---|
muteAudio(muted: boolean): void | Mutes or unmutes audio. |
toggleMuteAudio(): void | Toggles audio mute. |
muteVideo(muted: boolean): void | Mutes or unmutes video. |
toggleMuteVideo(): void | Toggles video mute. |
setVolume(volume: number, meetingParticipantId?: string): void | Sets the playback volume for one participant or for everyone. |
connect(mediaConfig?: { audio: boolean, video: boolean }): void | Connects media. Ignored if already connected. Participants connect automatically when they join. |
disconnect(): void | Disconnects media. Ignored if already disconnected. |
restartMediaConnections(): void | Restarts the media connections. Useful after a device change. |
enterVideoFullscreen(): void | Enters fullscreen. Fails in most browsers other than Chrome because fullscreen requires user interaction. |
| Method | Description |
|---|---|
getMediaDeviceSettings(): IMediaDeviceSettings | Returns the currently selected devices and resolution. |
getMediaDeviceAudioInputList(audioOnly: boolean): MediaDeviceInfo[] | Returns the available microphones. audioOnly limits the permission prompt to audio. |
getMediaDeviceAudioOutputList(audioOnly: boolean): MediaDeviceInfo[] | Returns the available speakers. audioOnly limits the permission prompt to audio. |
getMediaDeviceVideoInputList(): MediaDeviceInfo[] | Returns the available cameras. |
setAudioInputDeviceId(deviceId: string, reconnect: boolean): void | Sets the microphone. The API does not check that the device ID is valid. |
setAudioOutputDeviceId(deviceId: string, reconnect: boolean): void | Sets the speaker. Only Chrome and Edge support this today. |
setVideoInputDeviceId(deviceId: string, reconnect: boolean): void | Sets the camera. The API does not check that the device ID is valid. |
setVideoResolution(resolution: MeetingRoomVideoResolution, reconnect: boolean): void | Sets the outgoing video resolution. |
setMediaStreamConstraints(constraints: MediaStreamConstraints, merge?: boolean): void | Sets constraints for getUserMedia. merge defaults to true, so the room combines your constraints with ours. |
setDisplayMediaConstraints(constraints: MediaStreamConstraints, merge?: boolean): void | Sets constraints for getDisplayMedia. merge defaults to true. |
reconnect defaults to false. Pass true to apply the change immediately by reconnecting media.
The three device-list methods above can prompt the participant. They ask the browser for media permission to read device labels, so calling one can open a permission dialog. Call them when the participant expects it, not on page load.
| Method | Description |
|---|---|
setScreensharingInterceptor(callback: () => void): void | Lets an Electron application intercept screen-sharing requests and pre-select a source. |
startScreensharing(sourceId?: string): void | Starts screen sharing, optionally from a given source. Does nothing if the participant is not allowed to share. |
stopScreensharing(): void | Stops screen sharing. |
forceStopScreensharing(): void | Stops another participant's screen sharing. Moderators only; the server blocks it otherwise. |
startRecording(): void | Starts recording. Only works if the meeting is configured for partial recording. |
stopRecording(): void | Stops recording. Subject to the same condition. |
| Method | Description |
|---|---|
sendChatMessage(message: string): void | Sends a group chat message. |
sendPrivateChatMessage(message: string, participantId: string): void | Sends a private chat message to one participant. |
sendCustomMessage(message: ICustomMessage): void | Sends a custom message to all participants. They receive it as a customMessage event. |
| Method | Description |
|---|---|
setVideoDisplayCalculator(calculator: IVideoDisplayCalculator): void | Hands video layout to your own function. See video display calculator. |
on(event: MeetingRoomApiEvent, callback: ApiEventCallback): void | Subscribes to an event. |
If you are using Veeting Blocks rather than an embedded room, the API offers four more methods for driving the join flow yourself:
| Method | Description |
|---|---|
isBrowserSupported(): boolean | Returns whether the current browser can run a meeting. |
loadMeeting(meetingId: string): Promise<IMeetingRoomConfig> | Loads a meeting's configuration. |
joinMeeting(config: IMeetingConnectionConfig): Promise<void> | Joins the meeting. |
can(meetingPermission: string): Promise<boolean> | Returns whether the current participant has the given permission. |
interface IApiParticipant {
id: string;
name: string;
muted?: boolean;
handRaised?: boolean;
fromPSTN?: boolean;
hasVideo?: boolean;
hadVideo?: boolean;
joinedAt: number;
}
interface IApiChatMessage {
fromParticipantId?: string;
fromParticipantName: string;
message: string;
fromParticipantNameAnonymized?: boolean;
messageId?: string;
moderationState?: ChatModerationState; // "pending" | "approved" | "rejected" | "deleted"
}
interface IApiChatModeration {
messageId: string;
moderationState: ChatModerationState; // "pending" | "approved" | "rejected" | "deleted"
message: string;
fromParticipantName: string;
fromParticipantNameAnonymized?: boolean;
}
interface IApiRemainingTimeUpdate {
remainingSeconds: number;
}
interface IScreenshareState {
active: boolean;
}
interface IMediaDeviceSettings {
videoResolution: string;
audioInputDeviceId: string;
audioOutputDeviceId: string;
videoInputDeviceId: string;
}
interface IMeetingConnectionConfig {
meetingId: string;
participantName: string;
participantEmail?: string;
audio: boolean;
video: boolean;
moderatorToken?: string;
interpreterToken?: string;
invisibleToken?: string;
speakerToken?: string;
}
type MediaDirection = "receiveonly" | "sendonly" | "sendreceive";
type JoinMode = "audio-only" | "audio-video" | "video-only" | "no-media";
type ChatModerationState = "pending" | "approved" | "rejected" | "deleted";
type ApiEventCallback = (payload: void | IApiParticipant[] | IApiChatMessage
| IApiChatModeration | IApiRemainingTimeUpdate | IMeetingRoomConfig
| ICustomMessage | IScreenshareState) => void;Where the room anonymizes chat authors, IApiChatMessage has no fromParticipantId, and fromParticipantName is empty with fromParticipantNameAnonymized: true. In a moderated room a message can arrive pending and change state later; the chatModeration event announces each change. Its message is empty unless the new state is approved or deleted, so an approved event can be the first time you see a message that was held for moderation.
wlvmrApiReady after the meeting room has loaded, so it is never called.joined event. The room is loaded, but the participant has not joined yet.connected to mean media is flowing. It refers to the websocket to our API.startScreensharing to report a problem. If the participant is not allowed to share, it returns silently.startRecording on a meeting that is not configured for partial recording.forceStopScreensharing as a non-moderator. The server blocks it.Contact our team to discuss the details.