# Veeting developer documentation > Veeting Rooms is browser-based, GDPR-compliant video conferencing and collaboration from Veeting AG, hosted in Switzerland. It runs on WebRTC with no downloads or plug-ins, and can be white-labelled onto a customer's own domain or embedded into their own application through the Veeting Blocks API. Every developer-documentation page, in full. Individual pages are also available as Markdown at their own URL with .md appended. --- # Veeting Rooms REST APIs Source: https://www.veeting.com/en/developer-documentation/api-usage ## Overview The Veeting REST API lets your own system create accounts, schedule meetings on behalf of your users, and manage those meetings throughout their lifecycle. It is a plain JSON API over HTTPS, so any HTTP client works. The examples below use `curl`. **Base URL:** `https:///api/v6` `` is your own web meeting domain. There is no shared or global host, so use the domain your instance runs on. **Authentication:** an `X-API-KEY` header on every request. **Content type:** `application/json` on every POST and PUT. ## Rules that apply to every request These four rules apply to every endpoint on this page. The second one surprises most integrators, so read it even if you skip the rest. 1. **The API key is a server-side secret.** Anyone who holds it has full control over your web meeting platform. Never put it in browser JavaScript, in a mobile app bundle, or in a public repository. If you need to start meetings from a browser, call the API from your own backend and pass only the result to the page. 2. **A successful call returns HTTP 200 *and* `responseCode: 0`.** Most endpoints return HTTP 200 even when the call failed and carry the real outcome in a negative `responseCode`. Checking only the HTTP status makes failures look like successes. Check both. 3. **Payload data is always nested under `data`.** Read your values from `data`, never from the top level. An error response has no `data` field at all. 4. **All timestamps are ISO 8601 in UTC**, with a `Z` suffix. The `timezone` field on a meeting only affects how times are printed in invitations. It does not shift `startTime` or `endTime`. ## Choosing an API key There are two kinds of key, and the one you hold determines what you can do. | | Account level | Instance level | | ------------------- | --------------------------- | ---------------------------------------------------------- | | Created in | `Account Settings` | White label settings | | Can create accounts | no | yes | | Can manage meetings | yes, within its own account | yes, for any user on the instance | | Permissions | always meetings only | selectable: accounts, users, meetings, reporting, branding | | Who may hold it | the account owner | only the operator of the white-label instance | > **Note:** Instance-level keys must never be shared with customers. They can create accounts and act for any user on the instance. Account-level keys are always restricted to the meeting endpoints, no matter what else you try to grant them. The platform enforces that restriction rather than relying on convention, which makes an account-level key safe to issue to an integrator who only needs to schedule meetings. Account-level keys also require the `accountLevelApiKeys` feature to be enabled on the white-label instance. If it is off, every call made with such a key is rejected. ### Acting on behalf of a user By default, a key acts as a synthetic API user that does not belong to any person. To act as a real meeting organizer instead, send one of these headers: ```http X-USER-ID: X-USER-EMAIL: ``` If you send both, `X-USER-ID` wins and `X-USER-EMAIL` is ignored, so send only one. Both key types support these headers. An instance-level key can act as any user on the instance. An account-level key can act only as an organizer of its own account; any other target is rejected. ## Identifiers A meeting has two identifiers, and they are not interchangeable. Swapping them is the most common mistake made against this API. | Name | Looks like | What it is for | | ---------------- | -------------------------- | ----------------------------------------------------------------------------- | | `data.id` | `5e945c36b863b82cefdddf54` | 24-character hex. Use it in every API call. | | `data.meetingId` | `9974-7653-8886-0485` | Dashed digits. Use it in the join URL and when showing the meeting to people. | > **Rule of thumb:** if it has dashes, it is for people; if it is 24 hex characters, it is for the API. `PUT /meeting` happens to accept either, but the other endpoints do not, so use `data.id` everywhere and you will always be right. Three more identifiers appear in responses: | Name | Source | | ---------- | ------------------------------------------------------------- | | Account ID | `data.id` of `POST /account` | | User ID | `data[].id` of `GET /account/admin/{accountId}` | | Room ID | `data.roomId` of a meeting, created together with the account | ## Create an account Creates an account and its first administrator in one call. **Instance-level keys only.** | Field | Type | Required | Notes | | ---------------------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | adminEmail | string | yes | Must be unique across the whole instance. An email address can exist only once, even in different accounts. | | adminFirstname | string | yes | | | adminLastname | string | yes | | | adminPreferredLanguage | string | yes | ISO 639-1, for example `en` or `de` | | accountType | string | yes | `trial`, `standard`, `business`, `professional`, `confidential`, `boardroom`, `classroom`, `boardroomClassroom`, `school`, `payAsYouGo`, `internal` | | sendPassword | boolean | yes | `true` emails a password to `adminEmail`. Use `false` while testing so you do not mail real people. | | paidUntil | ISO 8601 | no | The account stops working after this moment. | ```bash curl 'https:///api/v6/account' \ -X POST \ -H 'X-API-KEY: ' \ -H 'content-type: application/json' \ --data-binary '{"sendPassword":true,"accountType":"trial","adminFirstname":"Test","adminLastname":"User","adminEmail":"test-user@example.com","adminPreferredLanguage":"en","paidUntil":"2026-12-31T23:59:59.000Z"}' ``` ```json { "responseCode": 0, "data": { "id": "5e9459c5b863b82cefdddf4f", "name": "test-user@example.com", "accountType": "trial", "numberOfMeetingRooms": 1, "paidUntil": "2026-12-31T23:59:59.000Z", "accountRooms": [] } } ``` `data.id` is the Account ID. Keep it; the next call needs it. The response describes the account, not the administrator it created, so it does not contain that person's User ID. Fetch it with the next call. ## Retrieve the account administrator Returns the administrators of an account. **Instance-level keys only.** This is how you obtain the User ID of the administrator that `POST /account` created. ```bash curl 'https:///api/v6/account/admin/' \ -X GET \ -H 'X-API-KEY: ' ``` ```json { "responseCode": 0, "data": [ { "id": "5e9459c5b863b82cefdddf4e", "email": "test-user@example.com", "firstName": "Test", "lastName": "User", "preferredLanguage": "en", "timezone": "Europe/Zurich" } ] } ``` `data` is an array. On a freshly created account, it holds one entry, but an account that has been in use can have several, so match on the email address you expect rather than always taking `data[0]`. ## Create a meeting Only two fields are required. The rest are optional, and the platform fills in account defaults for anything you leave out. The example below sends the full set, which is usually what you want: a meeting with no `endTime` or `duration` is rarely what you intended. | Field | Type | Required | Notes | | ------------------- | -------------- | -------- | ------------------------------------------------------------------------------- | | topic | string | **yes** | | | startTime | ISO 8601 | **yes** | | | endTime | ISO 8601 | no | | | duration | number | no | Minutes. It is not derived from the times, so make it agree with them yourself. | | type | string | no | `standard`, `offTheRecord`, `boardroom`, `classroom`, `audiobridge` | | isRecurring | boolean | no | | | recurring | object | no | Send `{}` when `isRecurring` is `false`. See below for the schema. | | isRecorded | boolean | no | Recording is only available for certain meeting types. | | isDialin | boolean | no | | | invitedParticipants | array | no | `[]` for none. See below for the shape of an entry. | | meetingPermissionId | string or null | no | `null` uses the account default. | With an instance-level key, add `X-USER-ID` or `X-USER-EMAIL` to schedule on behalf of that organizer. ```bash curl 'https:///api/v6/meeting' \ -X POST \ -H 'X-API-KEY: ' \ -H 'X-USER-ID: ' \ -H 'content-type: application/json' \ --data-binary '{"topic":"My Meeting Topic","startTime":"2026-09-07T09:00:00.000Z","endTime":"2026-09-07T10:00:00.000Z","duration":60,"type":"standard","isRecurring":false,"isRecorded":false,"isDialin":false,"invitedParticipants":[],"recurring":{},"meetingPermissionId":null}' ``` ```json { "responseCode": 0, "data": { "id": "5e945c36b863b82cefdddf54", "meetingId": "9974-7653-8886-0485", "topic": "My Meeting Topic", "startTime": "2026-09-07T09:00:00.000Z", "endTime": "2026-09-07T10:00:00.000Z", "type": "standard", "roomId": "5e9459c5b863b82cefdddf50", "isActive": false, "isOpen": false, "isClosed": true, "accountId": "5e9459c5b863b82cefdddf4f", "addedByUserId": "5e9459c5b863b82cefdddf4e", "timezone": "Europe/Zurich" } } ``` Reading the response: - `data.id` is what every later call needs. - `data.meetingId` is what you show people, and what goes in the join URL. - **`isClosed` is `true` on a meeting you just created, and that is normal.** It means nobody can join yet. A meeting opens for moderators about an hour before it starts (`isPreOpen`) and for everyone about fifteen minutes before (`isOpen`). Do not treat it as a failed call and do not retry. ### Inviting participants Each entry in `invitedParticipants` is an object: | Field | Type | Required | Notes | | ---------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------- | | email | string | **yes** | Must be a valid email address. | | name | string | no | Shown in the invitation. | | sendInvite | boolean | **yes** | `true` emails this person an invitation with the join link. `false` adds them to the meeting without writing to them. | ```json "invitedParticipants": [ { "email": "anna@example.com", "name": "Anna Meier", "sendInvite": true } ] ``` Set `sendInvite` to `false` while testing, for the same reason as `sendPassword` on account creation: it is the difference between a test run and mailing a real person. ### The recurring object Send `{}` when `isRecurring` is `false`. When it is `true`: | Field | Values | | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | frequencyType | `never`, `daily`, `weekly`, `monthly`, `yearly` | | frequency | Number. How many units between occurrences: `2` with `weekly` means every other week. Capped at 52 for weekly, 12 for monthly, 10 for yearly. | | weekDays | Which days a weekly series falls on, as **two-letter codes**: `MO`, `TU`, `WE`, `TH`, `FR`, `SA`, `SU`. | | monthlyPattern | `day` repeats on the same day of the month; `nthWeekDay` repeats on the same weekday of the month, for example the second Tuesday. | | endsType | `never`, `after`, `on` | | endsAfter | Number of occurrences, when `endsType` is `after`. The count includes the first occurrence, so `12` gives twelve meetings in total. Capped at 365. | | endsOn | ISO 8601 date, when `endsType` is `on` | | exclude | Array of ISO 8601 dates to skip | > **`weekDays` takes `MO`, not `Monday`.** A value the platform does not recognize is dropped without an error, and a weekly series that ends up with no recognized day simply repeats on the day its `startTime` falls on. Nothing fails, so it is worth getting right the first time. A weekly standup, every Monday, twelve times: ```json { "isRecurring": true, "recurring": { "frequencyType": "weekly", "frequency": 1, "weekDays": ["MO"], "endsType": "after", "endsAfter": 12 } } ``` ## The join URL The API does not return a link. Build it yourself from the dashed `meetingId`: ```text https:///meeting/ ``` For the example above, that is `https:///meeting/9974-7653-8886-0485`. This is the same URL that Veeting puts in the invitation emails and calendar entries it sends. Two things to know about it: - It uses the dashed `meetingId`. The 24 character `id` does not work here. - The host is the domain your users open the meeting room on. On most instances, that is the same `` you call the API on, but a white-label instance can be configured with a different one, so take it from your own configuration rather than assuming. If the meeting is password-protected, the invitation link also carries the password hash so the recipient does not have to type it: ```text https:///meeting/?meetingAccessHash= ``` You can add [query parameters](/en/developer-documentation/query-parameters) to a join URL to preset the participant name, skip the device test, choose a layout, and more. ## Read a meeting back ```bash curl 'https:///api/v6/meeting/5e945c36b863b82cefdddf54' \ -X GET \ -H 'X-API-KEY: ' \ -H 'X-USER-EMAIL: ' ``` Returns the same meeting object that `POST /meeting` returned. It accepts the 24-character `id` only. You will need this more often than you might expect, because `PUT` replaces rather than patches: read the meeting, change the fields you care about, then send the whole object back. It is also how you reconcile after a web hook you may have missed, since [web hook delivery is attempted once](/en/developer-documentation/web-hooks) with no retry. The caller must be an organizer of the account that owns the meeting or be listed in its `invitedParticipants`. ## List meetings Three endpoints return lists of meetings. There is no `GET /meeting`, no `/meeting/list`, no `/meetings`, and no `/history`; these three are the whole of it. | Route | Covers | | ---------------------------- | ----------------------------------------------------- | | `GET /meeting/upcoming` | From 00:00 UTC today forward, with no upper bound | | `GET /meeting/past` | Meetings that have already ended, within a date range | | `GET /meeting//` | Any window you name, past or future | ### Upcoming meetings Everything from 00:00:00 UTC of the current day forward. ```bash curl 'https:///api/v6/meeting/upcoming' \ -X GET \ -H 'X-API-KEY: ' \ -H 'X-USER-EMAIL: ' ``` ```json { "responseCode": 0, "data": [ { "id": "5e945c36b863b82cefdddf54", "meetingId": "9974-7653-8886-0485", "topic": "My Meeting Topic", "startTime": "2026-09-07T09:00:00.000Z", "endTime": "2026-09-07T10:00:00.000Z", "type": "standard", "roomId": "5e9459c5b863b82cefdddf50", "isActive": false, "isOpen": false, "isClosed": false, "accountId": "5e9459c5b863b82cefdddf4f", "addedByUserId": "5e9459c5b863b82cefdddf4e", "timezone": "Europe/Zurich" } ] } ``` ### Past meetings Meetings that have already ended, inside the range you ask for. Whether a meeting falls in the range is decided by when it ended: the moment it was closed if it was closed early, otherwise its `endTime`. A range in the future returns `[]`. ```bash curl 'https:///api/v6/meeting/past?startDate=2026-08-01T00:00:00Z&endDate=2026-08-31T23:59:59Z' \ -X GET \ -H 'X-API-KEY: ' \ -H 'X-USER-EMAIL: ' ``` Five optional query parameters: | Parameter | Behavior | | ----------- | -------------------------------------------------------------------------------------------------------------------------- | | `startDate` | ISO 8601. Snapped down to 00:00:00.000 UTC of that day. | | `endDate` | ISO 8601. Snapped up to 23:59:59.999 UTC of that day. | | `topic` | Case-insensitive substring match on the topic. Punctuation matches literally. | | `email` | Address of a participant. Only part of the result is filtered by it, so do not rely on it. Filter by participant yourself. | | `recorded` | Only the exact string `true` filters to recorded meetings. `TRUE`, `1`, and anything else count as false. | ### Meetings in any period Past meetings can be filtered by start and end date ```text GET /api/v6/meeting// ``` Both path segments are required. Pass the literal string `null` in either segment to leave that end open; either segment, or both, may be `null`. An unparsable segment behaves exactly like `null`. ```bash curl 'https:///api/v6/meeting/2026-08-01T00:00:00Z/null' \ -X GET \ -H 'X-API-KEY: ' \ -H 'X-USER-EMAIL: ' ``` ## Update a meeting ```text PUT /api/v6/meeting// ``` The second path segment is a boolean, and it is required: - `true` sends an updated invitation to everyone in `invitedParticipants`. - `false` changes the meeting quietly. This is a replace, not a patch. Send the complete meeting object, including an `id` field repeating the meeting ID, because fields you leave out are not preserved. ```bash curl 'https:///api/v6/meeting/5e945c36b863b82cefdddf54/false' \ -X PUT \ -H 'X-API-KEY: ' \ -H 'X-USER-EMAIL: ' \ -H 'content-type: application/json' \ --data-binary '{"id":"5e945c36b863b82cefdddf54","topic":"My Updated Meeting Topic","startTime":"2026-09-07T09:30:00.000Z","endTime":"2026-09-07T10:30:00.000Z","duration":60,"type":"standard","isRecurring":false,"isRecorded":false,"isDialin":false,"invitedParticipants":[],"recurring":{},"meetingPermissionId":null}' ``` ## Close a meeting early Meetings close by themselves when they end. Close one early to end it immediately: anyone still in the room is removed. Closing is not deleting. The meeting record stays. ```bash curl 'https:///api/v6/meeting/close' \ -X POST \ -H 'X-API-KEY: ' \ -H 'X-USER-EMAIL: ' \ -H 'content-type: application/json' \ --data-binary '{"meetingId":"5e945c36b863b82cefdddf54"}' ``` > **Note:** Despite its name, the `meetingId` field in this body takes the 24-character `data.id`, not the dashed `data.meetingId`. ## Delete meetings ```bash curl 'https:///api/v6/meeting/5e945c36b863b82cefdddf54' \ -X DELETE \ -H 'X-API-KEY: ' \ -H 'X-USER-EMAIL: ' ``` You can delete several meetings in one call by separating their IDs with commas: ```text DELETE /api/v6/meeting/,, ``` You can include a `cancellationMessage` in the body; it appears in the cancellation notice sent to the invited participants. ## Errors An error response carries a negative `responseCode` and a `responseMessage`, and has no `data` field. Remember that the HTTP status is usually still 200. ```json { "responseCode": -44, "responseMessage": "Input validation failed", "errors": [] } ``` | responseCode | Meaning | What to do | | ------------ | --------------------------------------- | -------------------------------------------------------------------------------------------------------- | | 0 | Success | | | -1 | The platform is in a maintenance window | Retry later | | -11 | Not found | Check the ID, do not retry | | -22 | Application error | Do not retry blindly; the call did not do what you asked | | -33 | API error | Check the request shape | | -44 | Input validation failed | Fix the payload; `errors` says what failed | | -55 | Billing error | The account is not entitled to this action | | -99 | Authentication failed | Bad, inactive, or unauthorized API key, or a user header the key is not allowed to act for. Do not retry | Requests are rate-limited per IP address at 50 per second, with a short burst allowance. Above that, the platform returns **HTTP 429** rather than a `responseCode`. ## Common mistakes - Treating HTTP 200 as success without checking `responseCode`. - Passing the dashed `meetingId` where the 24-character `id` is required. - Building the join URL with `id` instead of `meetingId`. - Sending `X-USER-ID` together with `X-USER-EMAIL` and expecting the email to be used. - Calling `POST /account` with an account-level key. - Sending a `duration` that disagrees with `startTime` and `endTime`. - Sending local times without the `Z` suffix. - Reading `isClosed: true` on a newly created meeting as an error. - Sending only the changed fields to `PUT /meeting`, which clears the rest. - Putting the API key in client-side code. - Expecting paging or a stable order from the list endpoints. - Calling `/meeting/past` without `startDate` and `endDate` and expecting only past meetings. - Sending a bare date such as `2026-08-01` and expecting it to mean UTC midnight. - Sending a day-first date such as `01.08.2026`. - Treating a list `id` as unique when a recurring series repeats it once per occurrence. - Sending query parameters to `/meeting/upcoming`. - Calling `/meeting/null/null` to fetch every meeting. - Omitting `X-USER-ID` and `X-USER-EMAIL` on the list endpoints with an instance-level key. ## End-to-end With an instance-level key, provisioning a customer and scheduling their first meeting looks like this: ```text POST /account -> data.id Account ID GET /account/admin/ -> data[0].id User ID POST /meeting + X-USER-ID -> data.id use in API calls data.meetingId use in the join URL GET /meeting/upcoming -> data[] from today, unsorted GET /meeting/past + date range -> data[] finished meetings PUT /meeting// update POST /meeting/close end early DELETE /meeting/ remove ``` With an account-level key, skip the first two steps and post straight to `/meeting`. --- # Custom tools Source: https://www.veeting.com/en/developer-documentation/custom-tools ## Overview Custom tools put your own interface inside the meeting room, alongside video, documents, and the whiteboard. A tool is an icon, a label, and one of your pages displayed in an iFrame. You configure them in the system settings of a white-label instance: **one configuration per instance**, and none by default. ## Choosing a source There are two ways to supply a tool, and each instance uses one of them. | Source | What it does | Use it when | | ------------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------- | | iFrame URL | One fixed URL, shown to everyone | The tool is the same for every participant | | API endpoint | We call your endpoint for each participant, and you return the tools they should see | Tools differ per participant, or you want more than one | Whichever you choose, the tool must be enabled for the instance before anything appears. ## A fixed iFrame URL ![Custom tools iFrame configuration](/assets/img/documentation/custom-tools-iframe-configuration.png) The URL must be served over HTTPS, and your web server must send headers that let a browser display it inside our page. ### The label The label is either a fixed string or a pipe-separated list of translations. | Label | Result | | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | My Custom Tool | Always reads "My Custom Tool", whatever language the meeting room is in. | | en:My Custom Tool\|de-CH:Mein eigenes Tool\|fr:Mon outil personnalise | English shows "My Custom Tool", German shows "Mein eigenes Tool", and so on. A language not in the list falls back to the English entry if there is one, otherwise to the first entry. | ### What we append to your URL We add query parameters so your page knows who is looking at it: | Parameter | Value | | --------------- | ------------------------------------------------------------------------------------------ | | participantName | The participant's name | | participantId | The unique ID of the meeting participant | | meetingId | The 24-character meeting ID, for example `5349b4ddd2781d08c09890f3` | | meetingToken | The dashed meeting number, for example `0000-0000-0000-0000` | | culture | The participant's locale when they entered the room, for example `en`, `en-US`, or `de-DE` | Two things to know: - **`moderatorToken` is not sent here.** It is only sent to an API endpoint (see below). A fixed iFrame cannot tell whether the viewer is a moderator. - If your URL already contains a `?`, the parameters are appended with `&`, so a URL with its own query string keeps working. Values are URL-encoded, so decode them before use. A participant name containing a space or an ampersand arrives intact. ## An API endpoint An API endpoint decides which tools each participant sees, and it can return up to five. ![Custom tools API configuration](/assets/img/documentation/custom-tools-api-configuration.png) When someone joins, we call your endpoint and show them whatever it returns. ![A browser joins the meeting, the meeting server calls your API endpoint with the participant and meeting details, your endpoint decides which tools that participant sees and returns up to five, and the meeting room shows them](/assets/img/documentation/custom-tools-api-workflow.en.svg) ```mermaid sequenceDiagram autonumber participant B as Web browser participant V as Web meeting server participant I as Your API endpoint B->>V: Joins the meeting V->>I: GET, with participant and meeting details Note over I: Decides which tools
this participant sees I-->>V: Returns up to five tools V-->>B: Returns the list of tools Note over B: Shows the tools
in the meeting room ``` ### The request We send an HTTP `GET` with your configured secret in an `X-API-KEY` header: | Parameter | Value | | --------------- | ---------------------------------------- | | participantName | The participant's name | | participantId | The unique ID of the meeting participant | | meetingId | The 24-character meeting ID | | meetingToken | The dashed meeting number | | moderatorToken | The participant's moderator token | > **Note:** `culture` is **not** sent to an API endpoint, and `moderatorToken` is **always** appended. For a participant who is not a moderator, it carries no valid token, so validate the value itself instead of relying on the parameter being present. Values are URL-encoded. ```bash curl 'https://?participantName=Joe%20Doe\ &participantId=XXXXX\ &meetingId=5349b4ddd2781d08c09890f3\ &meetingToken=0000-0000-0000-0000\ &moderatorToken=YYYYY' \ -H 'X-API-KEY: ' \ -H 'accept: application/json, text/plain, */*' ``` ### The response Respond with HTTP 200 and a **bare JSON array** of tool objects. Do not wrap it in an envelope: unlike the REST API, nothing here unwraps a `data` property for you. | Property | Meaning | | --------- | --------------------------------------------------------------------------------------------------------------------------- | | iFrameUrl | The URL to display. **A tool without one is skipped.** | | toolIcon | An SVG string for the icon. Anything longer than 50,000 characters is dropped, and the tool falls back to the default icon. | | labels | An array of label objects | A label object: | Property | Meaning | | -------- | --------------------------------------------------------------- | | culture | The locale of this label, for example `en`, `en-US`, or `de-DE` | | label | The text to display, for example "My Custom Tool" | ```json [ { "iFrameUrl": "https://www.example.com/custom-tool-1", "toolIcon": "...", "labels": [ { "culture": "en-US", "label": "Custom tool 1" }, { "culture": "de", "label": "Spezialtool 1" } ] } ] ``` ### Limits and fallbacks - **At most five tools.** Anything beyond the fifth in your array is ignored. - A tool with no `iFrameUrl` is skipped entirely. - If `labels` is empty or missing, the tool is labeled "Custom tool". - If the meeting room cannot match the participant's locale, it uses the first label in your array. Put your preferred default first. - If your endpoint errors, times out, or returns something that is not an array, no custom tools are shown. The meeting continues normally. ## Common mistakes - Swapping `meetingId` and `meetingToken`. `meetingId` is the 24-character one; `meetingToken` is the dashed one. - Expecting `moderatorToken` on a fixed iFrame URL, where it is never sent. - Expecting `culture` at an API endpoint, where it is never sent. - Treating the presence of `moderatorToken` as proof the participant is a moderator. - Wrapping the API response in an envelope instead of returning a bare array. - Returning more than five tools and wondering where the rest went. - Serving the tool over HTTP or with headers that forbid framing. - Reading a participant name straight from the query string without decoding it. --- # External meeting authorization service Source: https://www.veeting.com/en/developer-documentation/external-meeting-authorization-service ## Overview Meetings can be restricted in several ways. One option is to hand the decision to a service of your own, so that who may join is governed by the rules your systems already enforce, whether those rules live in a customer portal, a patient record, or a case file. This page describes how to build that service. ## How the exchange works The flow is a three-legged redirect, similar in shape to OAuth. 1. A participant opens a restricted meeting. The platform mints a **request token** and redirects the browser to your service, passing that token along. 2. Your service authenticates the person however you like and decides whether they may join. 3. If they may join, your service exchanges the request token for an **access token** in a server-to-server call. 4. Your service redirects the browser back to the meeting with the access token attached. That token grants the browser session access to the meeting. ![A browser opens a restricted meeting, is redirected to your service with a request token, your service authenticates the person and exchanges the token for an access token, then redirects the browser back to the meeting](/assets/img/documentation/external-meeting-authorization-service-workflow.en.svg) ```mermaid sequenceDiagram autonumber participant B as Browser participant V as Web meeting server participant S as Your service B->>V: Opens a restricted meeting V-->>B: Redirect, with a request token B->>S: Arrives with the request token Note over S: Authenticates the person
and decides S->>V: Exchanges the request token
(server to server) V-->>S: Access token S-->>B: Redirect back to the meeting,
with the access token B->>V: Joins with the access token ``` The request token proves the platform sent the participant to you. The access token proves you sent them back. ## Step 1: Receive the redirect Implement an endpoint that responds to `HTTP GET`. We redirect the browser to it with at least these query parameters: | Parameter | Description | | ------------ | ----------------------------------------------------------------------- | | hostname | The hostname of the meeting platform, for example `meeting.example.org` | | meetingId | The 24-character meeting ID, for example `5f521a93c20ff6721fbb6a6c` | | meetingToken | The dashed meeting number, for example `0000-0000-0000-0000` | | requestToken | A long random string that is valid for this meeting only | We may append additional parameters, so read the ones you need by name rather than by position. If your endpoint is `https://external.example.org/auth`, the browser arrives at: ```text https://external.example.org/auth?hostname=webmeeting.example.com&meetingId=5f521a93c20ff6721fbb6a6c&meetingToken=8320-2640-2482-3499&requestToken=dedf1722-661f-4004-9aaf-d3e56c498859-a27fd10f-b697-4c83-bca0-cb764cfd6c43 ``` ## Step 2: Exchange the request token After you decide that the participant may join, call us to exchange the request token for an access token: ```text GET https:///api/v6/meeting-room/auth//access-token// ``` | Placeholder | Value | | ------------- | ---------------------------------------------- | | HOSTNAME | The `hostname` from the redirect | | SECRET | The shared secret you configured (see below) | | MEETING-ID | The 24-character `meetingId` from the redirect | | REQUEST-TOKEN | The `requestToken` from the redirect | We check that the request token was issued for that meeting. On success: ```json { "responseCode": 0, "data": { "meetingId": "5f521a93c20ff6721fbb6a6c", "accessToken": "81430667-540e-4755-b32a-b5c51f704c7b-03526573-1494-48fb-a648-e80073275976" } } ``` This call follows the usual REST API conventions, so **check `responseCode` as well as the HTTP status** and read the token from `data.accessToken`. See [API usage](/en/developer-documentation/api-usage) for the response envelope. > **This call carries your secret in the URL.** Make the call server to server, never from the browser, and never log the full URL. ## Step 3: Redirect back to the meeting Send the browser to the meeting room with the access token attached: ```text https:///meeting//join?meetingAccessToken= ``` | Placeholder | Value | | ------------- | ------------------------------------------------------------------------- | | HOSTNAME | The `hostname` from the redirect | | MEETING-TOKEN | The **dashed** meeting number from the redirect, not the 24-character one | | ACCESS-TOKEN | The `accessToken` you just received | You can add [query parameters](/en/developer-documentation/query-parameters) to smooth the arrival, most usefully the values you already know from authenticating the person: ```text https:///meeting//join?meetingAccessToken=&participantName=&participantEmail= ``` ## Configuring the platform In **Platform Settings**, open **System Configuration**, then **Meeting Room**, and set the default authentication type for meetings to **External Service**. Two fields appear. ![Configuration of external meeting authorization service](/assets/img/documentation/external-meeting-authorization-service-configuration.png) Enter the full URL of your endpoint in "URL of external authentication server" and your chosen secret in "API Key of external authentication server". The secret is stored encrypted, and every token exchange is verified against it. The secret is yours to invent. Treat it like a password: long, random, and rotated if it ever leaks. ## A minimal implementation ```javascript const express = require("express"); const app = express(); const port = 3000; // Invent this yourself and configure the same value in the platform. const SECRET = process.env.VEETING_SHARED_SECRET; app.get("/auth", async (req, res) => { const { hostname, meetingId, meetingToken, requestToken } = req.query; // Decide here whether this person may join, using your own session, // directory or customer database. Redirect them away if they may not. if (!(await mayJoin(req, meetingId))) { return res.status(403).send("Not authorized to join this meeting"); } // Server to server: this URL contains the shared secret. const url = `https://${hostname}/api/v6/meeting-room/auth/${SECRET}` + `/access-token/${meetingId}/${requestToken}`; const response = await fetch(url).then((r) => r.json()); // HTTP 200 is not enough on its own, check responseCode too. if (response.responseCode !== 0) { return res.status(502).send("Could not obtain an access token"); } const accessToken = response.data.accessToken; res.redirect(`https://${hostname}/meeting/${meetingToken}/join` + `?meetingAccessToken=${encodeURIComponent(accessToken)}`); }); app.listen(port); ``` ## Common mistakes - Redirecting to the wrong path: the meeting room is at `/meeting//join`. - Using the 24-character `meetingId` in the redirect back, which needs the dashed `meetingToken`. The exchange call is the other way around. - Checking only the HTTP status of the exchange call and not `responseCode`. - Calling the exchange endpoint from the browser, which exposes your secret. - Logging the exchange URL, which also exposes your secret. - Letting the participant reach your endpoint without actually authenticating them: the request token proves where they came from, not who they are. --- # iFrame and Web Components Source: https://www.veeting.com/en/developer-documentation/iframe-and-web-components ## Overview There are two ways to put a Veeting meeting into your own application: embed the whole meeting room in an iFrame, or place individual pieces of it on your page as components. This page covers both and helps you choose. - **An iFrame** gives you the complete meeting room, including the join screen, in a few lines of HTML. It is the fastest route and the right default. - **[Veeting Blocks](/en/veeting-blocks/introduction)** gives you the individual components (video, chat, whiteboard, participants, and more) to arrange in your own layout. Use it when the meeting has to look like part of your product rather than a window inside it. Both are driven at runtime by the same [JavaScript API](/en/developer-documentation/javascript-apis). ## Embedding the meeting room in an iFrame ### First, get iFrame embedding enabled **iFrame embedding is off by default, which is why most first attempts show an empty frame.** Every instance has a feature flag, `allowIFrameEmbedding`. While the flag is off, the meeting room responds with an `X-Frame-Options: SAMEORIGIN` header, and the browser refuses to display the room inside a page on any other domain. Ask us or your reseller to enable it for your instance. It is a server-side setting, not something you can switch from your own page. ### The markup There is no special embedding URL. Use the normal meeting room URL, the same one participants would open directly: ```html ``` **The `allow` attribute is not optional.** Without it, the browser denies the meeting room access to the camera and microphone, and the participant joins with no media and no useful error. Copy the list above exactly. Add [query parameters](/en/developer-documentation/query-parameters) to the `src` URL to preset the participant's name, skip the join screen, choose a layout, hide sections, and more. Anything you can configure from a URL works the same inside an iFrame. ### When the frame stays blank In order of likelihood: 1. **`allowIFrameEmbedding` is off.** Open the meeting room URL directly and look at the response headers. If `X-Frame-Options: SAMEORIGIN` is present, that is the answer. 2. **The page is served over HTTP.** The meeting room allows framing only from HTTPS origins. 3. **The `allow` attribute is missing or incomplete**, so the room loads but the participant has no camera or microphone. 4. **A Content-Security-Policy on your own page** blocks the frame. Check `frame-src` in your policy, not ours. ## Web components Alongside Veeting Blocks, there are a few standalone web components: custom HTML tags that embed one piece of meeting functionality without an iFrame and without a build step. You configure them entirely through HTML attributes. > **Which should I use?** For new work, prefer [Veeting Blocks](/en/veeting-blocks/components). It covers far more of the meeting room, is the component set we are actively developing, and has a full JavaScript API. The standalone components remain available for existing integrations. A component is two things: an HTML element and a script that brings it to life. The script must come after the element. ### The element Attributes configure the element. The example here uses the Coffee Table component: | Attribute | Description | | ---------------- | ------------------------------------------------------------------------------------ | | api-host | The hostname of your meeting room instance, for example `https://rooms.veeting.com`. | | participant-name | The name shown to others in the meeting. Use `Anonymous` if you do not know it. | | meeting-id | The meeting the component connects to. Use the dashed meeting number. | | css-url | Optional. A URL to an external stylesheet, so you can restyle the component. | ```html ``` ### The scripts You need two scripts. The first is the official WebRTC adapter, which smooths over browser differences. The second is the component itself. Load it from your own meeting room instance so that it always matches your platform version. ```html ``` ## Common mistakes - Embedding an iFrame before `allowIFrameEmbedding` is enabled, then reading the empty frame as a code problem. - Leaving out the `allow` attribute, so the participant joins with no camera or microphone. - Serving the embedding page over HTTP. - Passing the 24-character meeting `id` to `meeting-id`, which expects the dashed number. - Loading the component script from an instance other than the one in `api-host`. - Putting the component script before the element it brings to life. - Starting new work on the standalone components rather than Veeting Blocks. --- # JavaScript APIs Source: https://www.veeting.com/en/developer-documentation/javascript-apis ## Overview 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. ## Where your code runs determines how you reach the API 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. ## Driving the room from the page around it ### Receiving events The meeting room posts every event to its parent window. Listen for those messages: ```javascript 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://") { 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. ### Calling methods Post a message to the iFrame that names the method and its arguments: ```javascript const room = document.getElementById("meeting-frame"); room.contentWindow.postMessage({ action: "muteAudio", payload: [true] }, "https://"); ``` `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: - **You get no return value.** Anything beginning with `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`. - **You get no error either.** If you misspell the method name, nothing happens and nothing is reported. Check the name against the tables below. ### A complete example A mute button and participant counter in your own page, outside the iFrame: ```html 0 participants ``` ## Getting the handler 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. ```javascript 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: 1. **Define `wlvmrApiReady` before the meeting room loads.** If you assign it afterward, the call has already happened and you never receive the handler. 2. **Do not hard-code the handler name.** The meeting room passes it to you as an argument. Always use `window[handler]`, never a literal. ## A worked example Waiting for the room, subscribing to events, then calling methods: ```javascript 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); }; ``` ## Events 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:** `connected` and `disconnected` describe the websocket to our API, not the WebRTC media. A participant can be disconnected from media and still use the whiteboard. ## Methods ### Meeting and participants | 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. | ### Audio and video | 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. | ### Devices | 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. ### Screen sharing and recording | 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. | ### Messaging | 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. | ### Layout and events | Method | Description | | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `setVideoDisplayCalculator(calculator: IVideoDisplayCalculator): void` | Takes over how videos are arranged. See [video display calculator](/en/developer-documentation/video-display-calculator). | | `on(event: MeetingRoomApiEvent, callback: ApiEventCallback): void` | Subscribes to an event. | ## Additional methods in Veeting Blocks 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` | Loads a meeting's configuration. | | `joinMeeting(config: IMeetingConnectionConfig): Promise` | Joins the meeting. | | `can(meetingPermission: string): Promise` | Whether the current participant has the given permission. | ## Type definitions ```typescript 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; ``` ## Common mistakes - Assigning `wlvmrApiReady` after the meeting room has loaded, so it is never called. - Hard-coding the handler name instead of using the one passed to the callback. - Calling a method before the `joined` event, when the room is loaded but not yet in the meeting. - Expecting `connected` to mean media is flowing. It refers to the websocket to our API. - Expecting `startScreensharing` to report a problem. If the participant is not allowed to share, it returns silently. - Calling `startRecording` on a meeting that is not configured for partial recording. - Calling `forceStopScreensharing` as a non-moderator. The server blocks it. - Passing a device ID without checking that it exists. The API does not validate it. --- # Query parameters Source: https://www.veeting.com/en/developer-documentation/query-parameters ## Overview Query parameters configure a meeting room directly from its URL. They let you control an embedded meeting room without writing any code, which is especially useful when you place the room in an iFrame in your own application. Append them to a meeting URL: ```text https:///meeting/?participantName=Test%20User&meetingPassword=1234 ``` **Values must be URL-encoded.** Otherwise, a name containing a space, an email address, or anything with `&` or `=` in it breaks the rest of the URL. Boolean parameters take the literal strings `true` and `false`. ## Two things to know before you start **The platform removes some parameters from the URL.** Anything that carries a token or personal data is stripped, either immediately or before the browser navigates. That is deliberate: those values then do not persist in browser history, in a bookmark, or in a `Referer` header. The lists are at the end of this page, and they matter if you expect to read a parameter back out of the address bar later. **Not every parameter the platform accepts is listed here.** The others are internal, serve support tooling, or carry meeting access tokens that we do not describe publicly. If you need one that is not documented, ask us rather than relying on one you found by reading the bundle: an undocumented parameter carries no compatibility promise. ## Participant details These parameters pre-fill the "Join Meeting" screen so the participant does not have to type the values. | Parameter | Possible values | Default | Description | | ---------------- | ----------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | culture | Language or locale code | the browser's language | The interface language. Use either a bare language code, such as `de`, or a locale, such as `de-CH` or `es-LA`. A bare code resolves to your instance's regional variant, so `de` becomes `de-CH`. The setting applies only if your instance offers that language. If it does not, the room uses the participant's browser language; if the instance offers none of those languages, it uses its own default language. Supplying `culture` also overrides a language the participant chose on an earlier visit. | | participantName | Any string | empty | The participant's name. Required if `skipJoinMeetingScreen` is `true`. | | participantEmail | Any email address | empty | The participant's email address. | | meetingPassword | Any string | empty | The meeting password. | | registerEmail | Any email address | empty | Only relevant if "Free Trial Registration" is enabled. Pre-fills the email field of the trial registration form. | | registerFrom | Any string | empty | Only relevant if "Free Trial Registration" is enabled. Adds a "from" field to the notification sent to the system administrator, which helps you track the source. | ## Access and authentication | Parameter | Possible values | Default | Description | | ------------------ | --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------- | | moderatorToken | Valid moderator token | empty | Gives the participant moderator rights in the meeting. | | jwtToken | Any JWT token | empty | A user's authentication token. Useful when authentication happens in a third-party tool. | | meetingAccessToken | Valid access token | empty | The access token for a specific meeting. Required if an external meeting authorization service is configured. | | meetingAccessHash | Valid access hash | empty | Carries the meeting password so the participant does not have to type it. This is what Veeting puts in the links it emails out. | > **Note:** These values are credentials. Generate them per participant and keep them out of shared links. See "Parameters the platform removes" below: the platform strips most of them from the URL for exactly that reason. ## The "Join Meeting" screen | Parameter | Possible values | Default | Description | | ----------------------------- | --------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | skipJoinMeetingScreen | true, false | false | Skips the "Join Meeting" screen entirely.
**IMPORTANT:** You must also supply `participantName`; otherwise the participant has no name. | | skipDeviceTestOnJoin | true, false | false | Sets the default value of the "Device Test" option. The participant can still change it. | | hideDeviceTestSection | true, false | false | Hides the "Device Test" section. Hiding the section does not change what the option would have done. | | hideJoinModeSection | true, false | false | Hides the "Join Mode" section. Combine it with `joinType` to choose the mode for the participant. | | hideConsentSection | true, false | false | Hides the consent checkbox.
**IMPORTANT:** You remain responsible for obtaining consent by other means. | | hideLegalAndComplianceSection | true, false | false | Hides the legal and compliance section, which contains the imprint and privacy links. | | disableNameInput | true, false | false | Disables the participant **name** input field. | | disableEmailInput | true, false | false | Disables the participant **email** input field. | | disablePasswordInput | true, false | false | Disables the meeting password input field. | ## Layout and media | Parameter | Possible values | Default | Description | | ----------------------- | ------------------------------------------------------------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------ | | meetingRoomLayout | standard, video-only, collaboration-only | standard | Controls whether video, the collaboration tools, or both are shown. | | joinType | audio-only, audio-video, video-only, no-media | audio-video | Pre-selects the join mode. Useful together with `hideJoinModeSection`. | | mediaDirection | receiveonly, sendonly | empty | Restricts the direction media flows. Omit it to both send and receive, which is the default. Any other value is ignored. | | cameraDirection | user, environment | empty | The default camera on mobile phones. `user` selects the front-facing camera, `environment` the rear one. | | videoResolution | 1280x960, 1280x720, 960x720, 960x540, 640x480, 640x360, 320x240, 320x180 | account default | The outgoing video resolution. | | disableIncomingVideo | true, false | false | Joins without receiving video from other participants. Useful on constrained connections. | | hideCollaborationHeader | true, false | false | Hides the header above the collaboration area, giving an embedded room more usable height. | | enableDarkMode | true, false | false | Renders the meeting room in dark mode. | ## Devices | Parameter | Possible values | Default | Description | | ------------------- | --------------- | ------- | ---------------------------------------------- | | videoInputDeviceId | string | empty | The camera device ID to use, if available. | | audioInputDeviceId | string | empty | The microphone device ID to use, if available. | | audioOutputDeviceId | string | empty | The speaker device ID to use, if available. | ## In the meeting | Parameter | Possible values | Default | Description | | -------------- | --------------- | ------- | -------------------------------------------------------------------------------------------------- | | enableFollowMe | true, false | false | Enables the "Follow Me" feature. It works only if the participant is the organizer or a moderator. | | leaveUrl | Any URL | empty | Overrides where the "Leave Meeting" button sends the participant. | ## Embedding, testing, and support | Parameter | Possible values | Default | Description | | ------------------------- | --------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | localStoragePrefix | Any string | empty | Prefixes the keys Veeting writes to `localStorage`. Set it if you embed more than one Veeting application on the same origin, so that their stored state does not collide. | | testWithoutRenderingMedia | true, false | false | For testing only: sends and receives streams without rendering audio and video. | | enableDebugLogs | true, false | false | Turns on verbose logging in the browser console. Useful when we are helping you diagnose something. | ## Parameters the platform removes The platform takes two sets of parameters out of the URL. Neither is a bug, but both will surprise you if you expect to read a value back from `window.location` later. **Removed immediately, always.** These never remain in the address bar: ```text meetingAccessToken meetingAccessRequestToken meetingChallengeCode ``` **Removed before the browser navigates.** The platform reads these first and then clears them, so they do not end up in history, in a bookmark, or in a `Referer` header sent to a third party: ```text skipJoinMeetingScreen jwtToken moderatorToken invisibleToken interpreterToken authenticationSkipToken participantName participantUuid participantEmail ``` If you need a value after joining, keep it in your own application rather than reading it back from the URL. ## Common mistakes - Forgetting to URL-encode a value. A space in `participantName` is the most common case. - Setting `skipJoinMeetingScreen=true` without `participantName`, which leaves the participant unnamed. - Hiding the consent section and assuming consent has been handled. - Expecting `moderatorToken` or `participantName` to still be in the URL after the meeting room has loaded. - Sending `1` or `yes` for a boolean. Only `true` and `false` are recognized. - Translating a parameter name. Parameter names are identifiers and are always in English. - Using an undocumented parameter found by reading the bundle. Undocumented parameters carry no compatibility promise. --- # Video display calculator Source: https://www.veeting.com/en/developer-documentation/video-display-calculator ## Overview The Video Display Calculator lets you take over how participant videos are arranged. Instead of using our layout, you supply a function that receives the container size and the participants, and returns an absolute position for every video. It is part of the [JavaScript API](/en/developer-documentation/javascript-apis), so everything on that page about getting the handler applies here too. | Term | Meaning | | --------- | -------------------------------------------------------------- | | Video | The video stream of one participant | | Container | The area of the meeting room in which all videos are displayed | ## The primary and secondary areas The presentation layout splits the container into two areas. The classic layout has no secondary area, and the primary one always fills the container. By default, every video sits in the primary area, which covers the whole container. A moderator can move videos into the secondary area; as soon as one video is there, the container is split. ![Primary and secondary area](/assets/img/documentation/video-display-calculator-areas.png) The secondary area can sit against any edge, and it comes in two sizes: | Value | Meaning | | ------------------- | ---------------------------------------------------------------------------------- | | `secondaryPosition` | `none`, `left`, `top`, `right`, or `bottom` | | `secondarySize` | `0.15` for the small area, `0.5` for the large one, as a fraction of the container | You receive both values. Honoring them is up to you, and so is the split: you get raw pixel dimensions for the whole container, not for each area. ## Writing the calculator Your function is called whenever positions need to be recalculated: on join, on leave, on resize, and when the moderator rearranges the room. It must return synchronously. The meeting room has **12 video containers**. You decide which are used and which stay hidden. ### What you receive The parameters are positional, so their names are yours to choose. They arrive in this order: | Order | Parameter | Type | Meaning | | ----- | ---------------------------- | ----------------------------- | --------------------------------------------------- | | 1 | containerWidth | number | Width of the video container, in pixels | | 2 | containerHeight | number | Height of the video container, in pixels | | 3 | hasSecondaryArea | boolean | Whether the secondary area is visible | | 4 | secondaryPosition | SecondaryVideoPosition | Which edge the secondary area sits against | | 5 | secondarySize | SecondaryVideoSize | `0.15` or `0.5` | | 6 | primaryParticipants | string[] | Participant IDs in the primary area | | 7 | secondaryParticipants | string[] | Participant IDs in the secondary area | | 8 | participantsOrder | string[] | All participant IDs, in display order | | 9 | participantsMediaInformation | IParticipantsMediaInformation | Media state and video dimensions per participant | | 10 | hasSelfView | boolean | Whether a self view is present | | 11 | isSelfviewInSecondary | boolean | Whether the self view belongs in the secondary area | | 12 | isTVMode | boolean | Whether the room is in TV mode | > **The self view is not in any participant array.** If `hasSelfView` is `true` and you want to show it, add a position for the participant ID `myself`. Nothing else adds it for you. ### What you return | Property | Type | Meaning | | ---------------- | ---------------- | ----------------------------------------------------------------------- | | videoPositions | IVideoPosition[] | One entry per video you want displayed | | hiddenContainers | boolean[] | 12 entries. `false` shows the container at that index; `true` hides it. | The two arrays are matched by index: the first entry of `videoPositions` applies to container `0`, and so on. **A position with `hiddenContainers[i]` left at `true` will not appear**, which is the single most common mistake here. An `IVideoPosition` is: | Property | Type | Meaning | | ------------- | -------- | ----------------------------------------------------------------- | | participantId | string | Whose video goes in this container, or `myself` for the self view | | width | number | Width in pixels | | height | number | Height in pixels | | top | number | Pixels from the top of the container | | left | number | Pixels from the left of the container | | zIndex | number | Stacking order, if you overlap videos | | cssClasses | string[] | Optional. **Every class must start with `video-container-`.** | ### If your calculator fails If your calculator throws or returns an empty object, the meeting room silently falls back to its built-in layout. Nobody sees an error, so if your layout does not appear, check the browser console first. ## Example This places the first participant in the center, the others in a row above, and the self view below. ![Example video display](/assets/img/documentation/video-display-calculator-example.png) It uses only the primary area. ```javascript const videoDisplayCalculator = { calculatePositions: (containerWidth, containerHeight, hasSecondaryArea, secondaryPosition, secondarySize, primaryParticipants, secondaryParticipants, participantsOrder, participantsMediaInformation, hasSelfView, isSelfviewInSecondary, isTVMode) => { const result = { videoPositions: [], hiddenContainers: Array(12).fill(true) }; // We want to display the videos with a 4:3 format const threeToFour = 3 / 4; // The main video is placed in the center, 65% of the container size const mainContainerSizePercentage = 0.65; let mainContainerWidth = 0; let mainContainerHeight = 0; let mainContainerTop = 0; let mainContainerLeft = 0; if (Array.isArray(participantsOrder) && participantsOrder.length > 0) { mainContainerWidth = containerWidth * mainContainerSizePercentage; mainContainerHeight = mainContainerWidth * threeToFour; if (mainContainerHeight > containerHeight * mainContainerSizePercentage) { mainContainerHeight = containerHeight * mainContainerSizePercentage; mainContainerWidth = mainContainerHeight / threeToFour; } mainContainerTop = (containerHeight - mainContainerHeight) / 2; mainContainerLeft = (containerWidth - mainContainerWidth) / 2; // Adding the main video result.videoPositions.push({ participantId: participantsOrder[0], width: mainContainerWidth, height: mainContainerHeight, top: mainContainerTop, left: mainContainerLeft, zIndex: 1 }); result.hiddenContainers[0] = false; const numberOfOtherVideos = participantsOrder.length - 1; if (numberOfOtherVideos > 0) { // Adding all other videos in the top row let otherContainersWidth = containerWidth / numberOfOtherVideos; let otherContainersHeight = otherContainersWidth * threeToFour; if (otherContainersHeight > mainContainerTop) { otherContainersHeight = mainContainerTop; otherContainersWidth = otherContainersHeight / threeToFour; } const otherContainersTop = (mainContainerTop - otherContainersHeight) / 2; let otherContainersLeft = (containerWidth / 2) - ((otherContainersWidth * numberOfOtherVideos) / 2); for (let i = 1; i < participantsOrder.length; i++) { result.videoPositions.push({ participantId: participantsOrder[i], width: otherContainersWidth, height: otherContainersHeight, top: otherContainersTop, left: otherContainersLeft, zIndex: 1 }); result.hiddenContainers[i] = false; otherContainersLeft += otherContainersWidth; } } } if (hasSelfView) { // The self view is never in participantsOrder, so add it explicitly. const selfViewHeight = containerHeight - mainContainerTop - mainContainerHeight; const selfViewWidth = selfViewHeight / threeToFour; result.videoPositions.push({ participantId: "myself", width: selfViewWidth, height: selfViewHeight, top: containerHeight - selfViewHeight, left: (containerWidth / 2) - (selfViewWidth / 2), zIndex: 1 }); // Reveal the container this position was pushed into. Without this the // self view is calculated correctly and then never shown. result.hiddenContainers[result.videoPositions.length - 1] = false; } return result; } }; window["wlvmrApiReady"] = (handler) => { if (!window[handler]) { console.error(`Handler window.${handler} not found!`); return; } window[handler].setVideoDisplayCalculator(videoDisplayCalculator); }; ``` ## Type definitions ```typescript type SecondaryVideoPosition = "none" | "left" | "top" | "right" | "bottom"; enum SecondaryVideoSize { small = 0.15, large = 0.5 } interface IVideoPosition { participantId: string; width: number; height: number; top: number; left: number; zIndex: number; cssClasses?: string[]; } interface IVideoDisplayConfig { videoPositions: IVideoPosition[]; hiddenContainers: boolean[]; } interface IMediaInformation { audioMuted?: boolean; videoMuted?: boolean; hasAudio?: boolean; hadAudio?: boolean; hasVideo?: boolean; // Bad connectivity sometimes stops the video: hasVideo is false and // hadVideo is true, which means we expect it to come back. hadVideo?: boolean; videoWidth?: number; videoHeight?: number; } interface IParticipantsMediaInformation { [participantId: string]: IMediaInformation; } ``` ## Common mistakes - Pushing a position but leaving its `hiddenContainers` entry at `true`, so the video never appears. - Forgetting the self view, which is in no participant array and must be added as `myself`. - Expecting an error when the calculator throws. The room falls back silently to the built-in layout. - Returning more than 12 positions. - Using a CSS class that does not start with `video-container-`. - Assuming `secondarySize` is always `0.15`. It is `0.5` for the large secondary area. - Doing asynchronous work. The function must return a result synchronously. --- # Web hooks Source: https://www.veeting.com/en/developer-documentation/web-hooks ## Overview Web hooks let Veeting call your system when something happens in a meeting, so you do not have to poll the API. When an event fires, the platform sends an HTTP request to a URL you configure, with the affected object as the JSON body. Web hooks are configured in two places, and both fire for the same event: - **White-label instance level**, in the "Web hooks" section of the System Configuration. Fires for every account on the instance. - **Account level**, per account. Fires only for that account. If you configure both, your endpoint is called twice for the same event, once from each level. ## Rules that apply to every web hook 1. **Delivery is attempted once.** There is no retry, no backoff, and no dead-letter queue. If your endpoint is down, times out, or answers with an error status, the event is logged on our side and lost on yours. Design for that: acknowledge quickly, queue the work internally, and if you need certainty, read the meeting back with [`GET /meeting/{id}`](/en/developer-documentation/api-usage) rather than trusting that every event arrived. 2. **Answer quickly.** No delivery timeout is configured, so a slow endpoint holds a connection open rather than failing fast. Return a `2xx` as soon as you have accepted the payload, then do the work. 3. **The body is the object, not an envelope.** Unlike REST API responses, web hook payloads are not wrapped in `responseCode` and `data`. Two events differ: `onMeetingSummaryCreated` arrives wrapped in `responseCode` and `data`, and `onMeetingRecordingCreated` arrives as an object with the meeting nested inside it rather than as the meeting itself. Both are described below. 4. **Check the payload shape before reading it.** Some events send a single object and some send an array. They are listed below. ## Configuration Each web hook is configured separately, with four settings: | Setting | Meaning | | ------- | ----------------------------------------------------------------------------- | | enabled | Whether this web hook fires at all | | url | The endpoint we call | | method | `POST`, `PUT`, or `DELETE`. Empty or unrecognized values fall back to `POST`. | | apiKey | Optional. See below. | ### Protecting your endpoint Set `apiKey` and the platform sends it as an `X-API-KEY` header on every call, so your endpoint can reject requests that do not carry it. If you leave it empty, no header is sent. This is the only authentication on a web hook call. An endpoint without an API key can be called by anyone who learns its URL. `content-type: application/json` is always sent. ## The events There are fourteen events. | Event | Fires when | Payload | | ----------------------------------- | --------------------------------------------------------------------------------- | ------------------------- | | `onMeetingScheduled` | A meeting is created | Meeting | | `onMeetingUpdated` | A meeting is changed | Meeting | | `onMeetingClosed` | A meeting closes, automatically or manually | Meeting | | `onMeetingDeleted` | Meetings are deleted | **Array** of Meeting | | `onMeetingCancelled` | A single occurrence of a recurring meeting is canceled | `{ meeting, day }` | | `onMeetingJoined` | Somebody joins a meeting | MeetingParticipantsStatus | | `onMeetingLeft` | Somebody leaves a meeting | MeetingParticipantsStatus | | `onMeetingSummaryCreated` | A meeting summary is created, usually about five minutes after the meeting closes | MeetingSummary, wrapped | | `onMeetingRecordingCreated` | A recorded meeting has been merged | Recording | | `onMeetingsReminder15MinutesBefore` | 15 minutes before meetings start | **Array** of Meeting | | `onMeetingsReminder30MinutesBefore` | 30 minutes before meetings start | **Array** of Meeting | | `onMeetingsReminder60MinutesBefore` | 60 minutes before meetings start | **Array** of Meeting | | `onMeetingsReminder24HoursBefore` | 24 hours before meetings start | **Array** of Meeting | | `onMeetingsReminder48HoursBefore` | 48 hours before meetings start | **Array** of Meeting | Two things about the reminders are easy to miss: - They deliver an **array** because one reminder run covers every meeting due at that moment. Even a single meeting arrives as an array of one. - The scope differs by level. A white-label instance reminder receives every due meeting on the instance. An account-level reminder receives only that account's meetings, so the same run sends different arrays to different endpoints. ## Payloads ### Meeting Sent by `onMeetingScheduled`, `onMeetingUpdated`, and `onMeetingClosed`. The same object the REST API returns from `POST /meeting`, abridged here: ```json { "id": "5e945c36b863b82cefdddf54", "meetingId": "9974-7653-8886-0485", "topic": "My Meeting Topic", "startTime": "2026-09-07T09:00:00.000Z", "endTime": "2026-09-07T10:00:00.000Z", "duration": 60, "type": "standard", "roomId": "5e9459c5b863b82cefdddf50", "isRecurring": false, "isRecorded": false, "isDialin": false, "invitedParticipants": [], "agenda": "", "whitelabelId": "5c737902b377b0f7fbf81fce", "accountId": "5e9459c5b863b82cefdddf4f", "addedByUserId": "5e9459c5b863b82cefdddf4e", "addedByUserEmail": "test-user@example.com", "addedByUserName": "Test User", "timezone": "Europe/Zurich", "isActive": false, "isOpen": false, "isClosed": true } ``` The two identifiers behave exactly as they do in the [REST API](/en/developer-documentation/api-usage): use `id` when calling back into the API, and `meetingId` when building a link or showing the meeting to somebody. ### Array of Meeting Sent by `onMeetingDeleted` and by all five reminders. The same object as above, in an array: ```json [ { "id": "5e945c36b863b82cefdddf54", "meetingId": "9974-7653-8886-0485", "topic": "My Meeting Topic" } ] ``` On `onMeetingDeleted`, each meeting may also carry a `cancellationMessage` field with the text the organizer supplied when deleting the meeting. ### Canceled occurrence Sent by `onMeetingCancelled` when one occurrence of a recurring meeting is canceled rather than the whole series. The meeting is nested, and `day` identifies the occurrence: ```json { "meeting": { "id": "5e945c36b863b82cefdddf54", "meetingId": "9974-7653-8886-0485", "isRecurring": true }, "day": "2026-09-14" } ``` ### MeetingParticipantsStatus Sent by `onMeetingJoined` and `onMeetingLeft`: ```json { "meetingId": "5df78199c015b37195230596", "meetingToken": "0000-0000-0000-0000", "isNamedRoom": false, "participant": { "name": "Joe Doe", "email": "joe@example.com", "meetingParticipantId": "4edaf0d2-cb6c-42f1-a266-4ae7d971f2e6", "isModerator": false }, "totalNumberOfGuests": 3, "totalNumberOfModerators": 1, "whitelabelId": "5c737902b377b0f7fbf81fce", "accountId": "5e9459c5b863b82cefdddf4f" } ``` > **Note:** in this payload, `meetingId` holds the 24-character meeting `id`, and `meetingToken` holds the dashed number. That is the opposite of the naming used everywhere else, so read these fields carefully. ### MeetingSummary Sent by `onMeetingSummaryCreated`. This is the one payload delivered wrapped in `responseCode` and `data`: ```json { "responseCode": 0, "data": { "id": "5f7d49eb62e8f9a43b23f986", "meetingId": "5f7d49e662e8f9a43b23f983", "meetingType": "boardroom", "agenda": "", "minutes": "", "participants": [ { "name": "Participant 1", "durations": [ { "action": "joined", "timestamp": 1602046445811 }, { "action": "left", "timestamp": 1602046670808 } ], "info": { "browserName": "Chrome", "browserVersion": "82.0.4062.0", "osName": "macOS", "osVersion": "10.15.6", "platformType": "desktop", "platformVendor": "Apple" } } ], "documents": [], "pdf": null, "accountId": "5c73790ab377b0f7fbf81fde", "isNamedRoom": false, "isRecordingPrepared": false, "sfuHostname": "ch-01-sfu-02.wlvmr.net", "meetingQuestions": [], "meetingConversationDuration": 25, "meetingConversationStartToEndDuration": 25, "meetingParticipantsStartToEndDuration": 35, "meetingStartToEndDuration": 60 } } ``` The four duration fields measure different things and are all in seconds: | Field | What it measures | | ------------------------------------- | ----------------------------------------------------------------------- | | meetingConversationDuration | How long at least two people were in the room at the same time | | meetingConversationStartToEndDuration | From the first time two people were in the room together until the last | | meetingParticipantsStartToEndDuration | From the first person joining to the last person leaving | | meetingStartToEndDuration | The scheduled duration, including any extension | ### Recording Sent by `onMeetingRecordingCreated`. Unlike the other payloads, this one is a wrapper: the meeting and the summary are nested inside it rather than forming the body themselves. ```json { "meeting": { "id": "5e945c36b863b82cefdddf54", "meetingId": "9974-7653-8886-0485", "topic": "My Meeting Topic" }, "summary": { "id": "5f48a701ee0c388a879bffa7" }, "mergedRecordingUrl": "https:///prepared//.webm", "recordingUrls": [ "video-file-1.webm", "video-file-2.webm", "video-file-3.webm" ], "emailNotificationRecipients": ["organizer@example.com"] } ``` | Property | Meaning | | --------------------------- | ------------------------------------------------------------------------- | | meeting | The full meeting object, as described above | | summary | The meeting summary this recording belongs to | | mergedRecordingUrl | The single merged recording | | recordingUrls | The individual parts that were merged, as the recording host reports them | | emailNotificationRecipients | Who the platform is about to email about this recording | > **Note:** read the meeting's identifiers from `meeting.id` and `meeting.meetingId`. There is no `meetingId` at the top level of this payload. ## Common mistakes - Assuming a failed delivery will be retried. It will not. - Treating a reminder payload as a single meeting. All five reminders send an array. - Reading `onMeetingSummaryCreated` as a bare object. It is the one wrapped payload. - Reading `meetingId` in the join and leave payloads as the dashed number. In those payloads it is the 24-character `id`, and `meetingToken` holds the dashed number. - Configuring the same endpoint at both instance and account level and then treating the duplicate call as a bug. - Leaving `apiKey` empty on an endpoint that changes state. - Doing slow work before answering, because nothing times out the call for you. --- # Veeting Blocks - JavaScript and Typescript APIs Source: https://www.veeting.com/en/veeting-blocks/apis - [Veeting Blocks Introduction](/en/veeting-blocks/introduction) - [Veeting Blocks Components](/en/veeting-blocks/components) - [Javascript and Typescript APIs](#) - [Introduction](#introduction) - [Basics](#basics) - [Example](#example) - [Join meetings, connect, disconnect, leave meetings](#join-meetings-connect-disconnect-leave-meetings) - [Available APIs](#available-apis) - [Type definitions](#type-definitions) - [Interfaces](#interfaces) - [Enums](#enums) - [Types](#types) ## Introduction The Veeting Blocks API lets you interact with the meeting room: mute a participant, read the participants list, listen for events, send custom events, and more. After initializing Blocks, the only call you must make is `joinMeeting()`. It tells Blocks which meeting to join and whether to send audio and video. The Veeting Blocks API is available in Typescript and in plain Javascript. The documentation below uses Typescript, but every API works the same way in Javascript. The API becomes available once Blocks itself has finished initializing: ```Typescript // Configure the domain name of your Veeting white label instance const whitelabelDomain = "webmeeting.example.com" if (!Blocks.isInitialized()) { // Blocks.init() must only be called once! Blocks.init({ version: "latest", whitelabelDomain: whitelabelDomain, initialized: async () => { // Veeting Blocks is initialized, the APIs are now available console.log(Blocks.api.getMediaDeviceSettings()) } }); } ``` ## Basics The APIs let you interact with a meeting, for instance to mute and unmute the local user. You can also subscribe to events with `on(event: MeetingRoomApiEvent, callback: ApiEventCallback): void;` and react to them. ## Example This example shows how to subscribe to meeting room events and how to call meeting room APIs: ```Typescript Blocks.api.on(MeetingRoomApiEvent.beforeConnecting, (payload) => { console.log(`[Blocks API] - Received event 'beforeConnecting' with payload ${payload}`) }); Blocks.api.on(MeetingRoomApiEvent.participantsUpdated, (payload) => { console.log(`[WLVMR API] - Received event 'participantsUpdated' with payload ${JSON.stringify(payload)}`) }); Blocks.api.on(MeetingRoomApiEvent.chatMessage, (payload) => { console.log(`[WLVMR API] - Received event 'chatMessage' with payload ${JSON.stringify(payload)} `) }); Blocks.api.muteAudio(true); Blocks.api.muteVideo(true); Blocks.api.setAudioInputDeviceId("default", false); Blocks.api.setAudioOutputDeviceId("default", false); Blocks.api.setVideoInputDeviceId("default", true); ``` ## Join meetings, connect, disconnect, leave meetings Veeting is built around meetings. A meeting takes place in a room that participants join. The room opens at the meeting's start time. Regular meetings have a fixed end time that you can extend. The room of an ad-hoc meeting closes automatically once every participant has disconnected. Participants can join while the room is open. During a break, a participant can disconnect and connect again later. When the meeting is over, participants leave it. Leaving removes the meeting context completely, so a user can join a different meeting without reloading the Blocks. ## Available APIs | API definition | Description | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
getVersion(): string;
| Returns the current Veeting version string, for example 6.11.0 | |
isBrowserSupported(): boolean;
| Returns true if the web browser supports WebRTC and Veeting Blocks, otherwise false | |
loadMeeting(meetingId: string): Promise
| Loads the details of a meeting, for example to check whether the room is currently open. You do not have to load a meeting before joining it. | |
joinMeeting(connectionConfig: IMeetingConnectionConfig): Promise
| Joins a meeting. See below for the details of IMeetingConnectionConfig | |
connect(mediaConfig?: { audio: boolean; video: boolean }): void;
| Reconnects to the previously joined meeting after a disconnect(). Ignored if the user is already connected. Joining a meeting connects the user automatically. | |
disconnect(): void; 
| Disconnects from the meeting without removing the meeting context, so you can call connect() again later. Ignored if the user is already disconnected. Here "connected" refers to the WebRTC streams only: a disconnected user can still work on the whiteboard. | |
restartMediaConnections(): void;
| Restarts the media connections. Useful after a device change. | |
getParticipantsList(): IApiParticipant[];
| Returns the current participants list | |
enableFollowMe(enabled: boolean): void;
| Enables or disables follow me (requires moderator rights) | |
muteVideo(muted: boolean): void;
| Mutes and unmutes the outgoing video | |
toggleMuteVideo(): void;
| Flips the current video mute state. Use this when you have no state of your own to track, and muteVideo when you do. | |
muteAudio(muted: boolean): void;
| Mutes and unmutes the outgoing audio | |
toggleMuteAudio(): void;
| Flips the current audio mute state. | |
setVolume(volume: number, meetingParticipantId?: string): void;
| Sets the audio volume. The volume parameter needs to be a number value between 0 and 1. If no meetingParticipantId is provided the volume change is applied to all participants. | |
setMediaStreamConstraints(mediaStreamConstraints: MediaStreamConstraints, merge?: boolean): void;
| Sets the media stream constraints for getUserMedia calls. If merge is true, the constraints you pass are merged with the internal ones. The parameter `merge` defaults to true. | |
setDisplayMediaConstraints(displayMediaConstraints: MediaStreamConstraints, merge?: boolean): void;
| Sets the display stream constraints for getDisplayMedia calls. If merge is true, the constraints you pass are merged with the internal ones. The parameter `merge` defaults to true. | |
setVideoInputDeviceId(deviceId: string, reconnect: boolean): void;
| Sets the video input device. Note: the API does not check whether the device ID is valid | |
setAudioInputDeviceId(deviceId: string, reconnect: boolean): void;
| Sets the audio input device. Note: the API does not check whether the device ID is valid | |
setAudioOutputDeviceId(deviceId: string, reconnect: boolean): void;
| Sets the audio output device. Only Chrome and Edge support this API. Note: the API does not check whether the device ID is valid. | |
setVideoResolution(resolution: MeetingRoomVideoResolution, reconnect: boolean): void;
| Sets the main video resolution | |
getMediaDeviceSettings(): IMediaDeviceSettings;
| Retrieves the currently selected media devices | |
getMediaDeviceAudioInputList(audioOnly: boolean): MediaDeviceInfo[];
| Lists the microphones. Asks the browser for media permission first, so calling it can raise the permission prompt. Pass true to ask for the microphone alone, false to ask for microphone and camera together. | |
getMediaDeviceAudioOutputList(audioOnly: boolean): MediaDeviceInfo[];
| Lists the speakers, with the same permission behavior. Only Chrome and Edge let you select an output device. | |
getMediaDeviceVideoInputList(audioOnly: boolean): MediaDeviceInfo[];
| Lists the cameras. The argument is required to compile but has no effect here: this one always asks for microphone and camera, because a camera list is of no use without camera permission. | |
setScreensharingInterceptor(callback: () => void): void;
| Registers a callback that intercepts screensharing requests from users. An Electron application can use it to pre-select a device ID. | |
startScreensharing(sourceId?: string): void;
| Starts screensharing. An interceptor is not required. Passing a sourceId pre-selects the source, which is what an Electron application does after intercepting the request. Returns nothing and reports nothing: if the participant may not share, the call is silently ignored. | |
stopScreensharing(): void;
| Stops your own screensharing. An interceptor is not required. | |
startRecording(): void;
| Starts recording. Works only if the meeting is configured for partial recording (meeting.recordingType === 'partial') | |
stopRecording(): void;
| Stops recording. Works only if the meeting is configured for partial recording (meeting.recordingType === 'partial') | |
forceStopScreensharing(): void;
| Forcefully stops screensharing. Moderators only: the server blocks the event when a non-moderator calls it | |
enterVideoFullscreen(): void;
| Opens the video container in fullscreen. *Note*: entering fullscreen requires a user interaction, so this call fails in most browsers other than Google Chrome | |
sendChatMessage(message: string): void;
| Sends a group chat message | |
sendPrivateChatMessage(message: string, participantId: string): void;
| Sends a private chat message to the participant with the ID participantId | |
sendCustomMessage(message: ICustomMessage): void;
| Sends a custom message to all participants | |
leaveMeeting(): void;
| Disconnects from the server and leaves the meeting room | |
setVideoDisplayCalculator(videoDisplayCalculator: IVideoDisplayCalculator): void;
| Lets you define how the videos are laid out. See Video Display Calculator for details | |
can(meetingPermission: string): Promise;
| Queries the Meeting Permissions service to check whether the user may use a given tool, for example "agenda.view" or "screensharing.view" | |
on(event: MeetingRoomApiEvent, callback: ApiEventCallback): void;
| Registers a listener for meeting room events | ## Type definitions These input and output types are used by the Javascript API. ### Interfaces ```Typescript interface IMeetingConnectionConfig { // The meeting ID in the form of 0000-0000-0000-0000 meetingId: string; // Optional, to make a user a moderator moderatorToken?: string; // Optional, for interpreters in multi-language-channel meetings interpreterToken?: string; // Optional, for silent participants invisibleToken?: string; // The name of the participant, visible to all participants participantName: string; // Optional, to receive the meeting summary participantEmail?: string; // Set to true if audio should be sent audio: boolean; // Set to true if video should be sent video: boolean; } interface IApiParticipant { id: string; name: string; joinedAt: number; muted?: boolean; handRaised?: boolean; fromPSTN?: boolean; hasVideo?: boolean; hadVideo?: boolean; } 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 IVideoPosition { participantId: string; width: number; height: number; top: number; left: number; zIndex: number; // Important: CSS class names MUST be prefixed with 'video-container-' cssClasses?: string[] } interface IVideoDisplayConfig { videoPositions: IVideoPosition[], hiddenContainers: boolean[] } interface IVideoDisplayCalculator { calculatePositions( containerWidth: number, containerHeight: number, secondaryPosition: SecondaryVideoPosition, secondaryDisplay: SecondaryDisplay, secondarySize: SecondaryVideoSize, primaryParticipants: string[], secondaryParticipants: string[], participantsOrder: string[], hasSelfView: boolean, isSelfviewInSecondary: boolean, isTVMode: boolean ): IVideoDisplayConfig; } interface IMeetingRoomConfig { isNamedRoom: boolean; meetingId: string; roomId: string; id: string; topic: string; meetingType: MeetingType; dialInEnabled: boolean; passwordProtected: boolean; hasVideo: boolean; startTime: number; endTime: number; isOpen: boolean; isPreOpen: boolean; isClosed: boolean; isActive: boolean; isDemoMeeting: boolean; isPromoMeeting: boolean; isFreeMeeting: boolean; isRecorded: boolean; isAccountValid: boolean; maxNumberOfParticipants?: number; organizerId: string; accountId: string; logoFileName?: string; disableLogs?: boolean; broadcastingEnabled?: boolean; participantsEmail?: Availability; authType?: MeetingRoomAuthType; meetingRoomLayout?: MeetingRoomLayout; joinMeetingSound?: string; leaveMeetingSound?: string; closingMeetingSound?: string; closedMeetingSound?: string; } interface ICustomMessage { from?: string; to?: string; data?: any; onlyToModerators?: boolean; } ``` ### Enums ```Typescript enum MeetingRoomApiEvent { beforeConnecting = "beforeConnecting", connected = "connected", // websocket to veeting API established and inuse joined = "joined", // veeting room ready for usage disconnected = "disconnected", // websocket to veeting API disconnected or unused leave = "leave", participantsUpdated = "participantsUpdated", chatMessage = "chatMessage", privateChatMessage = "privateChatMessage", meetingDurationUpdated = "meetingDurationUpdated", meetingRoomConfigUpdated = "meetingRoomConfigUpdated", customMessage = "customMessage", screenshareStateChange = "screenshareStateChange" } enum SecondaryVideoSize { small = 0.15, large = 0.5 } enum MeetingRoomVideoResolution { "1280x960" = "1280x960", "1280x720" = "1280x720", "960x720" = "960x720", "960x540" = "960x540", "640x480" = "640x480", "640x360" = "640x360", "320x240" = "320x240", "320x180" = "320x180", "160x120" = "160x120" } enum MeetingType { standard = "standard", offTheRecord = "offTheRecord", boardroom = "boardroom", classroom = "classroom", audiobridge = "audiobridge" } enum MeetingRoomLayout { classic = "classic", presentation = "presentation", template = "template" } enum Availability { required = "required", optional = "optional", hidden = "hidden" } enum MeetingRoomAuthType { none = "none", invited = "invited", accountMember = "accountMember", platformMember = "platformMember", external = "external" } ``` ### Types ```Typescript type ApiEventCallback = ( payload: void | IApiParticipant[] | IApiChatMessage | IApiRemainingTimeUpdate | IMeetingRoomConfig | ICustomMessage | IScreenshareState ) => void type SecondaryVideoPosition = "none" | "left" | "top" | "right" | "bottom"; type SecondaryDisplay = "display" | "hidden"; ``` --- # Veeting Blocks - Components Source: https://www.veeting.com/en/veeting-blocks/components - [Veeting Blocks Introduction](/en/veeting-blocks/introduction) - [Veeting Blocks Components](#) - [How to run the Block Examples](#how-to-run-the-block-examples) - [Video Block](#video-block) - [Device Selection Block](#device-selection-block) - [Screensharing Block](#screensharing-block) - [Whiteboard Block](#whiteboard-block) - [Documents Block](#documents-block) - [Agenda Block](#agenda-block) - [Chat Block](#chat-block) - [Polls Block](#polls-block) - [Minutes Block](#minutes-block) - [Notes Block](#notes-block) - [Assistant Block](#assistant-block) - [Participants Block](#participants-block) - [Join Meeting Block](#join-meeting-block) - [Javascript and Typescript APIs](/en/veeting-blocks/apis) ## How to run the Block Examples Veeting Blocks always require an active meeting. You can schedule meetings through our web application or through our [REST APIs](/developer-documentation/api-usage). Every Block on this page is live. Join a meeting with the controls below, and the examples connect to it, so you can see what each component does before you write any code. The Blocks documented here are the ones you need for a meeting: video, audio, screensharing, whiteboard, documents, agenda, chat, polls, minutes, notes, the AI assistant, the participants list and the join form. The library ships more of them, including breakout rooms, webinar controls, language channels, the lobby and picture-in-picture. This page is not the full list. Ask us if you need one that is not here.

If you don't have an account yet and just want to try the Blocks, generate a demo meeting ID here:

If you have an existing meeting ID, copy it into the text field below and click Join meeting:

## Video Block ### Overview The video Block displays the videos of the meeting participants. ```html ``` ### Parameters | Name | Type | Description | | -------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | has-mute-control-buttons | Boolean | Optional, hides the mute buttons if set to false, defaults to true | | has-action-control-buttons | Boolean | Optional, hides the volume control and raise hand buttons if set to false, defaults to true | | video-only | Boolean | Optional, shows a single video and forces the "fill" layout. Setting it to false has no effect, so leave the attribute off instead. Defaults to false. | | container-layout | "standard" or "fill" | Optional, switches the video display layout between "standard" and "fill". Defaults to "fill". | ### Example > **Note:** Check out "[How to run the Block Examples](/veeting-blocks/components#how-to-run-the-block-examples)" to see how you can run this example.
## Device Selection Block ### Overview This Block displays a form where users select their audio and video devices. ```html ``` ### Parameters | Name | Type | Description | | ---------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------- | | show-audio-settings | Boolean | Optional, hides the microphone and speaker selection if set to false. Defaults to true. | | show-video-settings | Boolean | Optional, hides the camera selection if set to false, which is useful for audio-only meetings. Defaults to true. | | auto-apply-new-devices | Boolean | Optional, applies the newly selected devices immediately. If set to false, you must restart the media connections yourself. Defaults to false. | ### Events The following events are available to subscribe to. Example: ```javascript document.querySelector("vrb-device-selection") .addEventListener("selection", (event) => { const saved = event.detail; console.log("Have the devices changed?", saved ? "Yes" : "No"); }); ``` Blocks are custom elements, so an event arrives as a `CustomEvent` and its value is on `event.detail`. This is not the same `on()` you use on `Blocks.api`, which is our own API object rather than a DOM element. | Name | Callback type | Description | | --------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | selection | Boolean | Triggered when the user clicks "Save" or "Cancel". The callback parameter is true if the user saved the selection, false if the user canceled. | ### Note Changing the media devices stores the selected IDs in local storage. If the user is already in a meeting and you want the new devices to take effect right away, restart the media connections yourself: ```javascript document.querySelector("vrb-device-selection") .addEventListener("selection", (event) => { if (event.detail) { veeting.Blocks.api.restartMediaConnections(); } }); ``` ### Example > **Note:** Check out "[How to run the Block Examples](/veeting-blocks/components#how-to-run-the-block-examples)" to see how you can run this example.
## Screensharing Block ### Overview The screensharing Block displays the Meeting Screensharing tool. ```html ``` ### Parameters | Name | Type | Description | | ------------ | ------- | ------------------------------------------------------------- | | show-toolbar | Boolean | Optional, shows the screensharing toolbar, defaults to true | ### Example > **Note:** Check out "[How to run the Block Examples](/veeting-blocks/components#how-to-run-the-block-examples)" to see how you can run this example.
## Whiteboard Block ### Overview The whiteboard Block displays the Meeting Whiteboard tool. ```html ``` ### Parameters | Name | Type | Description | | ----------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------- | | show-reduced-width | Boolean | Optional, displays a smaller toolbar in the whiteboard, defaults to false | | show-external-whiteboard-link | Boolean | Optional, adds a QR code button to the toolbar for sharing the whiteboard with an external device, defaults to false | | show-toolbar | Boolean | Optional, shows the whiteboard toolbar, defaults to true | ### Example > **Note:** Check out "[How to run the Block Examples](/veeting-blocks/components#how-to-run-the-block-examples)" to see how you can run this example.
## Documents Block ### Overview The meeting documents Block displays the Documents Sharing tool. ```html ``` ### Parameters | Name | Type | Description | | ------------------ | ------- | ----------------------------------------------------------------------------------------- | | show-reduced-width | Boolean | Optional, displays a smaller toolbar in the documents viewer, defaults to false | | show-toolbar | Boolean | Optional, shows the documents toolbar, defaults to true | ### Example > **Note:** Check out "[How to run the Block Examples](/veeting-blocks/components#how-to-run-the-block-examples)" to see how you can run this example.
## Agenda Block ### Overview The meeting agenda Block displays the meeting agenda. ```html ``` ### Parameters | Name | Type | Description | | -------------------- | ------- | ----------------------------------------------------------------------------------------- | | show-toolbar | Boolean | Optional, shows the agenda toolbar, defaults to true | ### Example > **Note:** Check out "[How to run the Block Examples](/veeting-blocks/components#how-to-run-the-block-examples)" to see how you can run this example.
## Chat Block ### Overview The meeting chat Block displays the Meeting Chat. ```html ``` ### Parameters | Name | Type | Description | | ------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | with | String | Optional, the meeting participant ID of an active participant. With an ID, the Block shows a private chat with that participant. Without one, it shows the group chat that every participant sees. Defaults to the group chat. | | show-toolbar | Boolean | Optional, shows the chat toolbar, defaults to true | ### Example > **Note:** Check out "[How to run the Block Examples](/veeting-blocks/components#how-to-run-the-block-examples)" to see how you can run this example.
## Polls Block ### Overview The polls Block displays the Meeting Polls. ```html ``` ### Parameters | Name | Type | Description | | ------------ | ------- | ------------------------------------------------------------- | | show-toolbar | Boolean | Optional, shows the toolbar to manage polls, defaults to true | | shown-on-small-screen | Boolean | Optional, forces the compact layout that the Block otherwise uses only on narrow screens, defaults to false | ### Example > **Note:** Check out "[How to run the Block Examples](/veeting-blocks/components#how-to-run-the-block-examples)" to see how you can run this example.
## Minutes Block ### Overview The meeting minutes Block displays the Meeting Minutes. ```html ``` ### Parameters | Name | Type | Description | | -------------------- | ------- | ------------------------------------------------------------------------------------------ | | show-toolbar | Boolean | Optional, shows the minutes toolbar, defaults to true | ### Example > **Note:** Check out "[How to run the Block Examples](/veeting-blocks/components#how-to-run-the-block-examples)" to see how you can run this example.
## Notes Block ### Overview The meeting notes Block displays the Private Notes. ```html ``` ### Parameters | Name | Type | Description | | -------------------- | ------- | ---------------------------------------------------------------------------------------- | | show-toolbar | Boolean | Optional, shows the notes toolbar, defaults to true | ### Example > **Note:** Check out "[How to run the Block Examples](/veeting-blocks/components#how-to-run-the-block-examples)" to see how you can run this example.
## Assistant Block ### Overview The meeting assistant Block displays the Meeting Assistant. ```html ``` ### Parameters This Block takes no parameters. ### Example > **Note:** Check out "[How to run the Block Examples](/veeting-blocks/components#how-to-run-the-block-examples)" to see how you can run this example.
## Participants Block ### Overview The meeting participants Block lists everyone currently in the room. ```html ``` ### Parameters | Name | Type | Description | | ------------ | ------- | -------------------------------------------------------------------------------- | | hide-toolbar | Boolean | Optional, hides the toolbar for inviting additional participants, defaults to false | | show-filter | Boolean | Optional, shows the participants list filter, defaults to false | ### Example > **Note:** Check out "[How to run the Block Examples](/veeting-blocks/components#how-to-run-the-block-examples)" to see how you can run this example.
## Join Meeting Block ### Overview The join meeting Block displays the standard join screen. Use it instead of joining through the Veeting Blocks APIs. ```html ``` ### Parameters | Name | Type | Description | | ---------------- | ------ | --------------------------------------------------------------------------- | | meeting-id | String | Required, the meeting ID of the meeting to join | | meeting-password | String | Optional, the meeting password which should be pre-filled in the form input | ### Events The following events are available to subscribe to. Example: ```javascript document.querySelector("vrb-join") .addEventListener("success", () => { console.log("Meeting joined successfully"); }); ``` Blocks are custom elements, so an event arrives as a `CustomEvent` and any value is on `event.detail`. Note that event names keep their original spelling: attributes are dash-cased, event names are not. | Name | Callback type | Description | | -------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | meetingLoaded | IMeetingRoomConfig | Triggered when the meeting has loaded. Gives you the meeting room configuration, for instance to check whether the meeting is currently open. | | success | void | Triggered after the user has joined the meeting. | | cancel | void | Triggered when the user clicks the logo on the join screen. Use it to send the user somewhere else. | | authenticate | {meetingId: string, meetingAuthType: MeetingRoomAuthType} | Triggered when the meeting requires additional authentication before the user can join. | --- # Veeting Blocks - Introduction Source: https://www.veeting.com/en/veeting-blocks/introduction - [Veeting Blocks Introduction](#) - [Introduction to Veeting Blocks](#introduction-to-veeting-blocks) - [Overview](#overview) - [Requirements](#requirements) - [Installation](#installation) - [Importing and initialization](#importing-and-initialization) - [Import with Typescript or ES6](#import-with-typescript-or-es6) - [Importing with plain Javascript](#importing-with-plain-javascript) - [Initialization](#initialization) - [Basic use case](#basic-use-case) - [Create a meeting](#create-a-meeting) - [Include Blocks to your HTML](#include-blocks-to-your-html) - [Initialize Blocks and join a meeting](#initialize-blocks-and-join-a-meeting) - [Examples](#examples) - [Demo workspaces](#demo-workspaces) - [Angular Demos](#angular-demos) - [Ionic Demo](#ionic-demo) - [Plain Javascript Demo](#plain-javascript-demo) - [Veeting Blocks Components](/en/veeting-blocks/components) - [Javascript and Typescript APIs](/en/veeting-blocks/apis) ## Introduction to Veeting Blocks Veeting Blocks are Web Components for all our Veeting collaboration tools. Use them to build your own video conferencing application or to add collaboration features to an existing one. There are Blocks for audio and video, screen sharing, a whiteboard, and more. You can use Veeting Blocks with plain __HTML, CSS and Javascript__, or with a framework such as __Angular__, __React__, __VueJS__ or __Ionic__. ## Overview Veeting Blocks are HTML elements that you add to your web application. The Veeting Blocks APIs let you join meetings and interact with those Blocks. Building an application or an integration is mostly a matter of laying out a user interface. You don't need to handle connectivity or WebRTC edge cases yourself. Blocks does that for you. This introduction shows you how to install Blocks and how to add them to your project. The demo applications at the bottom of this page give you a working starting point. ## Requirements These are the basic requirements to use Veeting Blocks: - `Node.js` and `npm` to install the loader package. Any version still supported by the Node project will do. - Familiarity with installing npm packages. - Experience building web application front ends. ## Installation Add our private npm registry to your workspace by adding a line to the `.npmrc` file in your project root. Create the file if it does not exist yet. ```bash @veeting:registry=https://releases.veeting.net/npm/ ``` Then install the Veeting Blocks loader: ```bash npm install @veeting/blocks-loader ``` The loader always pulls the most up-to-date code directly from our servers. It also ships Typescript definitions for all APIs. ## Importing and initialization ### Import with Typescript or ES6 Import Blocks into the file where you use it: ```Typescript import { Blocks, MeetingRoomApiEvent } from "@veeting/blocks-loader"; ``` The package exports the enums and the interface definitions alongside `Blocks`, so import whatever else you use. `MeetingRoomApiEvent` is needed by the join example below. ### Importing with plain Javascript If you don't use a build system, copy the contents of `node_modules/@veeting/blocks-loader` into your assets folder, for example `assets/js/blocks`, and include the Javascript file directly in your HTML. ```html ``` This script puts everything on the global `veeting` object, so every call becomes `veeting.Blocks.x` instead of `Blocks.x`. The two are otherwise the same API. ### Initialization Initialize Blocks before you use any Block or any API. Initialization loads the main Veeting Blocks code. > **Note:** You need access to a Veeting white label instance. Contact us if you don't have one yet. ```Typescript // Configure the domain name of your Veeting white label instance const whitelabelDomain = "rooms.veeting.com" if (!Blocks.isInitialized()) { // Blocks.init() must only be called once! Blocks.init({ version: "latest", whitelabelDomain: whitelabelDomain, initialized: async () => { // Veeting Blocks is initialized } }); } ``` ## Basic use case ### Create a meeting Veeting is built around meetings. Each meeting has a start date, an end date that you can extend, and a unique meeting ID. You need the meeting ID to join a meeting. You can generate meeting IDs in our web application or through our [REST APIs](/developer-documentation/api-usage). ### Include Blocks to your HTML The example below adds the audio and video Block and the whiteboard Block to a page. Every Block element starts with the prefix `vrb-`. ```html
``` > **Note:** A Veeting Block fills its parent container. Give the parent a size, or the Block renders at zero height and the page looks broken. ### Initialize Blocks and join a meeting The following code initializes Blocks and joins a meeting. As described above, you need a meeting ID to join. ```javascript // Configure the domain name of your Veeting white label instance const whitelabelDomain = "rooms.veeting.com" // We assume that you have generated the meeting ID elsewhere const meetingId = "0000-0000-0000-0000"; // Each Blocks participant needs a name. You can generate one randomly, // for instance with a UUID algorithm. const participantName = "Joe Doe"; /* * Note: if you loaded blocks.js into your HTML instead of importing the package, every * Blocks reference below becomes veeting.Blocks - veeting.Blocks.init({...}) and so on. */ if (!Blocks.isInitialized()) { // Blocks.init() must only be called once! Blocks.init({ version: "latest", whitelabelDomain: whitelabelDomain, initialized: async () => { // Optional, load the meeting to ensure that // the meeting exists and that the room is open const config = await Blocks.api.loadMeeting(meetingId); if (!config || !config.isOpen) { alert("Meeting not found or not open"); return; } // Subscribe before joining, so you do not miss the first events Blocks.api.on(MeetingRoomApiEvent.participantsUpdated, (participantsList) => { // The participants list has been updated }); // Join the meeting await Blocks.api.joinMeeting({ meetingId: meetingId, participantName: participantName, audio: true, video: true }) // Call APIs. The argument is required by the signature but has no effect // on this one, see the APIs page. const cameraDevices = Blocks.api.getMediaDeviceVideoInputList(false); } }); } ``` ## Examples ### Demo workspaces We provide `Visual Studio Code` workspaces for `Angular`, `Ionic` and `plain Javascript` demo applications. > **Note:** As with any WebRTC application that needs the camera and microphone, you must run these demos on localhost or on a host with HTTPS. Browsers block media access over plain HTTP. ### Angular Demos The Angular demo workspace contains three projects that you can run independently. The `Broadcast Button` project shows a basic webinar setup with one moderator and several participants. It uses only the [Video Block](/veeting-blocks/components#video-block), with mute buttons for audio and video. Install the dependencies with `npm install`, then run `npm run broadcast-button`. The `Coffee Table` project shows a basic video conference with several participants. It uses the [Video Block](/veeting-blocks/components#video-block) and implements the Video Display Calculator interface to arrange the videos in a circle instead of our standard layout. Install the dependencies with `npm install`, then run `npm run coffee-table`. The `Meeting Room` project is a complete meeting room that uses every Veeting Block. Install the dependencies with `npm install`, then run `npm run meeting-room`. Download the Angular workspace here: [Angular Workspace](https://releases.veeting.net/demo/downloads/veeting-blocks-angular-demo-20230111.tar.gz) ### Ionic Demo The Ionic demo workspace contains a small demo that uses the [Video Block](/veeting-blocks/components#video-block), [Chat Block](/veeting-blocks/components#chat-block) and [Whiteboard Block](/veeting-blocks/components#whiteboard-block). Install the dependencies with `npm install`, then run `npm start`. Download the Ionic workspace here: [Ionic (Angular) Workspace](https://releases.veeting.net/demo/downloads/veeting-blocks-ionic-demo-20230111.tar.gz) ### Plain Javascript Demo The plain Javascript demo is a small application built around the [Video Block](/veeting-blocks/components#video-block). It uses `Gulp` as its build system. Install the dependencies with `npm install`, then run `npm start`. Download the Javascript workspace here: [Javascript Workspace](https://releases.veeting.net/demo/downloads/veeting-blocks-javascript-demo-20251229.tar.gz)