The JavaScript API lets your own page drive a running meeting room: mute a participant, read the participants list, send chat messages, change devices, 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 whether my code runs INSIDE the room (Veeting Blocks,
a custom tool) or in the page around an embedded iFrame. The two
reach the API completely differently and the answer changes
everything you write.
2. Wait for the "joined" event before calling methods that need the
participant to be in the meeting.
3. "connected" and "disconnected" refer to the websocket to our API,
not to WebRTC media. Do not treat them as media state.
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:This choice matters more than anything else on this page, so settle it first.
| Your code runs | How you reach the API |
|---|---|
| Inside the meeting room, which means Veeting Blocks or a custom tool page | Directly, through the handler. You get the full API, including 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 twelve 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 leave it out.
Because messaging across the boundary is one-way, two limits apply:
get is therefore not useful 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 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>The rest of this section applies to code running inside the meeting room: Veeting Blocks or a custom tool. If you are embedding an iFrame, use the messages above instead.
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 twelve 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 websocket to the Veeting API is disconnected or unused | none |
leave | The 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 |
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:
connectedanddisconnecteddescribe the websocket to our API, not the WebRTC media. A participant can be disconnected from media and still use the whiteboard.
| Method | Description |
|---|---|
getVersion(): string | The version string of the running meeting room. |
getParticipantsList(): IApiParticipant[] | The current participants list. |
leaveMeeting(): void | Disconnects from the server and leaves the meeting room. |
enableFollowMe(enabled: boolean): void | Enables 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 | The currently selected devices and resolution. |
getMediaDeviceAudioInputList(audioOnly: boolean): MediaDeviceInfo[] | The available microphones. audioOnly limits the permission prompt to audio. |
getMediaDeviceAudioOutputList(audioOnly: boolean): MediaDeviceInfo[] | The available speakers. audioOnly limits the permission prompt to audio. |
getMediaDeviceVideoInputList(): MediaDeviceInfo[] | 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 | Constraints for getUserMedia. merge defaults to true, which combines your constraints with ours. |
setDisplayMediaConstraints(constraints: MediaStreamConstraints, merge?: boolean): void | Constraints for getDisplayMedia. merge defaults to true. |
reconnect defaults to false. Pass true to apply the change immediately by reconnecting media.
The three list methods 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, delivered as a customMessage event. |
| Method | Description |
|---|---|
setVideoDisplayCalculator(calculator: IVideoDisplayCalculator): void | Takes over how videos are arranged. 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 | 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> | 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;
}
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 ApiEventCallback = (payload: void | IApiParticipant[] | IApiChatMessage
| IApiRemainingTimeUpdate | IMeetingRoomConfig | ICustomMessage | IScreenshareState) => void;wlvmrApiReady after the meeting room has loaded, so it is never called.joined event, when the room is loaded but not yet in the meeting.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.