Skip to main content

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 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 -d

This starts:

ContainerRole
jitsi-webBrowser frontend (port 8443)
jitsi-xmppProsody XMPP server (signalling)
jicofoConference focus component
jvbVideo bridge (UDP 10000 — must be reachable from participants)
Open port 10000/UDP

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:

  1. The JVB must advertise your real public domain, not its own internal default. docker-compose.meetings.yaml's jvb service already sets this for you:

    JVB_WS_DOMAIN: meet.hrida.ai   # must match XMPP_DOMAIN above
    JVB_WS_SERVER_ID: jvb1

    If 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 literally localhost:8443 in 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
  2. 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 1006 immediately 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/Connection headers 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​

  1. Go to Admin Panel → Settings → Meetings.
  2. Click + Add Plugin → select jitsi.
  3. Fill in:
FieldValue
Namee.g. Production Jitsi
Jitsi Domainmeet.hrida.ai
App IDhrida-ai (matches JITSI_JWT_APP_ID)
JWT Secretvalue of JITSI_JWT_APP_SECRET
Room Prefixoptional — e.g. hrida- to namespace rooms
  1. 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​

  1. Log in to marketplace.zoom.us.
  2. Create a Server-to-Server OAuth app.
  3. Note your Account ID, Client ID, and Client Secret.
  4. Add scopes: meeting:write, meeting:read.

Step 2 — Create an Events API integration (for agent-triggered meetings)​

  1. Add an Events API integration to a Zoom service.
  2. Copy the Integration Key (also called routing key).

Step 3 — Add the Zoom plugin in Hrida.ai​

  1. Admin Panel → Settings → Meetings → + Add Plugin → zoom
  2. Fill in:
FieldValue
Account IDZoom Account ID
Client IDOAuth Client ID
Client SecretOAuth Client Secret
Events Routing KeyIntegration Key (for agent-created meetings)
Webhook Secret TokenFrom Zoom App → Feature → Event Subscriptions
  1. 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.

  1. In your Zoom app, go to Feature → Event Subscriptions → Add.
  2. Set:
    • Event notification endpoint URL: the Webhook URL shown on the Zoom plugin card
    • Subscribe to: meeting.ended, recording.completed, meeting.participant_joined
  3. Save.

Starting a Meeting​

From the UI​

  1. Go to Meetings in the sidebar.
  2. Click + New Meeting.
  3. Select the video plugin (if you have multiple) and enter a title.
  4. 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.

Requires a real triggering user

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​

KeyTypeRequiredDescription
domainstringYesJitsi domain, e.g. meet.hrida.ai
app_idstringYesJWT aud/iss claim, matches Jitsi config
jwt_secretsecretYesHS256 signing key
room_prefixstringNoPrepended to all room names (default: none)

Zoom​

KeyTypeRequiredDescription
account_idstringYesZoom Account ID
client_idstringYesS2S OAuth Client ID
client_secretsecretYesS2S OAuth Client Secret
routing_keysecretYesEvents API routing key
webhook_secret_tokensecretNo (but see below)HMAC secret for webhook validation
webhook_secret_token effectively required for webhooks to work

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.

Hrida.ai is proprietary software of Zlabs Innovation. See the license for terms. © 2026 Zlabs Innovation.