Video Conferencing Plugin
The video conferencing plugin creates browser-based meeting rooms with SSO authentication. Users who are logged into Hrida.ai are automatically signed into the meeting room — no separate login required.
Supported providers: Jitsi Meet (self-hosted) and Zoom (cloud).
Jitsi Meet (Recommended for Self-Hosted)
Jitsi Meet is open-source, runs entirely on your VPS, and integrates directly with Hrida.ai's Keycloak for SSO via JWT tokens.
Step 1 — Add environment variables
# Random strings — generate with: openssl rand -hex 32
JITSI_SECRET=<random>
JITSI_JWT_APP_ID=hrida-ai
JITSI_JWT_APP_SECRET=<random> # Used to sign meeting JWTs
JITSI_PUBLIC_URL=https://meet.hrida.ai
JITSI_DOCKER_HOST_ADDRESS=<your_VPS_public_IP>Step 2 — Start the Jitsi stack
docker compose \
-f docker-compose.yaml \
-f docker-compose.meetings.yaml \
up -dThis starts:
| Container | Role |
|---|---|
jitsi-web | Browser frontend (port 8443) |
jitsi-xmpp | Prosody XMPP server (signalling) |
jicofo | Conference focus component |
jvb | Video bridge (UDP 10000 — must be reachable from participants) |
The JVB video bridge needs UDP port 10000 open in your firewall and reachable from participants' browsers. If you're behind a NAT, also set JITSI_DOCKER_HOST_ADDRESS to your VPS's public IP.
Step 2.5 — Reverse proxy: WebSocket support for /colibri-ws/
jitsi-web only listens on ${JITSI_HTTP_PORT:-8081}/${JITSI_HTTPS_PORT:-8445} internally — you still need a reverse proxy in front of it for the public JITSI_PUBLIC_URL domain (e.g. meet.hrida.ai) to actually be reachable over HTTPS. This step is easy to skip because the Jitsi UI itself will load fine without it — the failure only shows up once you try to actually join a call.
The symptom, visible in the browser console:
WebSocket connection to 'wss://meet.hrida.ai/colibri-ws/jvb1/...' failed
[modules/RTC/BridgeChannel.js] <e.onclose>: Channel closed: 1006
Two separate things need to be correct, in order:
-
The JVB must advertise your real public domain, not its own internal default.
docker-compose.meetings.yaml'sjvbservice already sets this for you:JVB_WS_DOMAIN: meet.hrida.ai # must match XMPP_DOMAIN above JVB_WS_SERVER_ID: jvb1If this is missing, the JVB falls back to its own hardcoded default and hands the browser
wss://localhost:8443/colibri-ws/...— a URL that means "your own machine," not the server. If you ever see literallylocalhost:8443in the failing WebSocket URL, this is the cause. Applying a change here requires a full recreate, not just a restart:docker compose -f docker-compose.yaml -f docker-compose.meetings.yaml up -d --force-recreate jvb -
Your reverse proxy must forward the WebSocket upgrade for this path. Regular HTTP/HTTPS traffic (loading the Jitsi UI itself) works through almost any reverse proxy config — WebSocket upgrades need to be explicitly enabled. If the domain is right (step 1 above) but you still get connection close code
1006immediately on connect, the proxy is the next place to look, since it never even reaches the JVB.Nginx Proxy Manager: open the Proxy Host for your Jitsi domain → enable Websockets Support. This is off by default per Proxy Host — see the Nginx HTTPS reference (NPM tab) for the full setup.
Raw nginx: the block needs
Upgrade/Connectionheaders explicitly, and a longer timeout since colibri-ws is a long-lived connection held open for the whole call:location / { proxy_pass http://127.0.0.1:8081; # jitsi-web's HTTP port proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 1800; }Verify without guessing — this curl checks whether the upgrade handshake actually reaches something WebSocket-aware:
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" \ -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: test" \ https://meet.hrida.ai/colibri-ws/jvb1/A normal HTML/JSON response (not
HTTP/1.1 101 Switching Protocols) confirms the proxy is swallowing the upgrade before it reaches Jitsi.
If both of those are correct and you still see failures, check one layer deeper — jitsi-web's own internal nginx also needs ENABLE_WEBSOCKETS=1 (already set by the compose overlay) to have actually taken effect on that specific container:
docker exec hrida-jitsi-web env | grep ENABLE_WEBSOCKETS
docker exec hrida-jitsi-web cat /config/nginx/meet.conf | grep -A5 "colibri-ws"If that config block is missing despite the env var being set, the container was never recreated after the var was added — --force-recreate it, a plain restart won't pick up new environment values.
Step 3 — Verify Jitsi is running
https://meet.hrida.ai — direct Jitsi access
http://localhost:8443 — local access (HTTP only)
Step 4 — Add the Jitsi plugin in Hrida.ai
- Go to Admin Panel → Settings → Meetings.
- Click + Add Plugin → select jitsi.
- Fill in:
| Field | Value |
|---|---|
| Name | e.g. Production Jitsi |
| Jitsi Domain | meet.hrida.ai |
| App ID | hrida-ai (matches JITSI_JWT_APP_ID) |
| JWT Secret | value of JITSI_JWT_APP_SECRET |
| Room Prefix | optional — e.g. hrida- to namespace rooms |
- Click Save.
How JWT SSO Works
When a user starts or joins a meeting, Hrida.ai mints a short-lived JWT containing:
{
"context": {
"user": {
"id": "<user_id>",
"name": "Alice Smith",
"email": "alice@example.com",
"avatar": "https://..."
}
},
"aud": "hrida-ai",
"iss": "hrida-ai",
"sub": "meet.hrida.ai",
"room": "engineering-standup-ab12cd",
"exp": 1751234567
}The token is signed with JITSI_JWT_APP_SECRET using HS256. Jitsi validates it on connect — no additional login prompt.
Zoom Integration
Zoom meetings use Server-to-Server OAuth and the Zoom Events API v2.
Prerequisites
- A Zoom account with Marketplace access
- A Server-to-Server OAuth app (not a user OAuth app)
- The Events API v2 routing key from a service integration
Step 1 — Create a Zoom S2S OAuth app
- Log in to marketplace.zoom.us.
- Create a Server-to-Server OAuth app.
- Note your Account ID, Client ID, and Client Secret.
- Add scopes:
meeting:write,meeting:read.
Step 2 — Create an Events API integration (for agent-triggered meetings)
- Add an Events API integration to a Zoom service.
- Copy the Integration Key (also called routing key).
Step 3 — Add the Zoom plugin in Hrida.ai
- Admin Panel → Settings → Meetings → + Add Plugin → zoom
- Fill in:
| Field | Value |
|---|---|
| Account ID | Zoom Account ID |
| Client ID | OAuth Client ID |
| Client Secret | OAuth Client Secret |
| Events Routing Key | Integration Key (for agent-created meetings) |
| Webhook Secret Token | From Zoom App → Feature → Event Subscriptions |
- Click Save.
Step 4 — Configure Zoom webhooks
Zoom POSTs meeting.ended and recording.completed to Hrida.ai so sessions are automatically marked as ended and recording URLs are stored.
- In your Zoom app, go to Feature → Event Subscriptions → Add.
- Set:
- Event notification endpoint URL: the Webhook URL shown on the Zoom plugin card
- Subscribe to:
meeting.ended,recording.completed,meeting.participant_joined
- Save.
Starting a Meeting
From the UI
- Go to Meetings in the sidebar.
- Click + New Meeting.
- Select the video plugin (if you have multiple) and enter a title.
- Click Start — the meeting room opens with the video iframe.
From an agent
Any agent node in Agent Builder, or any Skill, can be granted the create_meeting built-in tool — no extra infrastructure required:
User: Start a team standup for the engineering group.
Agent: [calls create_meeting(title="Engineering Standup")]
→ Meeting 'Engineering Standup' created via jitsi.
Join URL: https://meet.hrida.ai/engineering-standup-ab12cd?jwt=...
By default it uses the first enabled meeting plugin; pass plugin_type to target a specific one (jitsi, zoom, or calendar to schedule on the built-in Calendar instead of starting a call immediately). It cannot start a Multi-Agent Room (plugin_type="agents") — use the UI for that.
create_meeting attributes the session to whoever's chat/workflow run triggered the agent. It will return an error if called from a context with no real user (e.g. an isolated skill test) — there's no way to fake or skip this, since meeting ownership and the My Sessions list depend on it being correct.
Broader meeting management as MCP tools (hrida-mcpo-meetings)
For broader agent access — listing sessions, joining existing rooms, updating or ending them — deploy the hrida-mcpo-meetings sidecar from the Docker Compose overlay. It's a purpose-built MCP server (not an auto-generated wrapper — see Agent Integration for why that distinction matters and the exact tool list) that runs as a dedicated service account rather than the triggering user.
Meeting Room Layout
The meeting room page (/meetings/room/{id}) has two panels:
┌─────────────────────── ─────┬──────────────────┐
│ │ │
│ Video iframe │ Notes panel │
│ (Jitsi / Zoom embed) │ (collapsible) │
│ │ │
└────────────────────────────┴──────────────────┘
Toggle the notes panel with the Show/Hide Notes button in the header. Notes update in real time when Whisper transcription completes after the meeting.
Config Schema Reference
Jitsi
| Key | Type | Required | Description |
|---|---|---|---|
domain | string | Yes | Jitsi domain, e.g. meet.hrida.ai |
app_id | string | Yes | JWT aud/iss claim, matches Jitsi config |
jwt_secret | secret | Yes | HS256 signing key |
room_prefix | string | No | Prepended to all room names (default: none) |
Zoom
| Key | Type | Required | Description |
|---|---|---|---|
account_id | string | Yes | Zoom Account ID |
client_id | string | Yes | S2S OAuth Client ID |
client_secret | secret | Yes | S2S OAuth Client Secret |
routing_key | secret | Yes | Events API routing key |
webhook_secret_token | secret | No (but see below) | HMAC secret for webhook validation |
Webhook verification fails closed: if webhook_secret_token is not set, every inbound Zoom webhook (meeting.ended, recording.completed, etc.) is rejected with 401, not silently accepted. The field is technically optional only in the sense that Zoom meetings can still be created and joined without it — but session-status updates and recording links will never arrive unless it's configured. Set it in Zoom → Feature → Event Subscriptions when you add the subscription.
Troubleshooting
"Meeting not found" after clicking Join → The session may have expired or the plugin was disabled. Check Admin Panel → Settings → Meetings to confirm the plugin is enabled.
Jitsi shows login page instead of auto-signing in
→ The JWT secret in the plugin config doesn't match JITSI_JWT_APP_SECRET. Both values must be identical.
Zoom meeting creation fails with 401 → The S2S OAuth token expired (they last 1 hour). This is refreshed automatically — if errors persist, verify the client ID/secret are correct.
No video in the room (connection issues)
→ UDP port 10000 may not be reachable. Check firewall rules and confirm JITSI_DOCKER_HOST_ADDRESS is set to your VPS's public IP.
WebSocket connection to 'wss://.../colibri-ws/...' failed, channel closes with code 1006
→ See Reverse proxy: WebSocket support for /colibri-ws/ above. If the failing URL literally contains localhost:8443, it's the JVB advertising the wrong domain (JVB_WS_DOMAIN missing or the container wasn't recreated after adding it). If the domain in the URL is already correct but it still fails, the reverse proxy in front of it isn't forwarding the WebSocket upgrade for that path.