Webinars
Needs review
Needs review: Selling a webinar: the product block on webinar resources is only filled when YouTube is configured and the webinar has a live stream (App\Providers\ShopServiceProvider). Check whether a webinar without YouTube can be bought end to end.
A webinar is a scheduled live session with one or more trainers. Learners who have access (usually by buying the webinar’s product) join a video room at the scheduled time. The room is a Jitsi room, either on a self-hosted Jitsi server or on 8x8 JaaS. Optionally the session is broadcast to YouTube as an unlisted live stream.
Three packages are involved:
| Package | Role |
|---|---|
webinar |
Webinar model, admin and learner endpoints, reminders, trainer assignment events |
jitsi |
Builds the room parameters and the signed JWT for Jitsi or JaaS; receives the recording webhook |
pencil-spaces |
Pencil Spaces client. Not used by webinars; it creates rooms for approved consultation requests |
Who uses it
Section titled “Who uses it”- Administrators create webinars, assign trainers and manage the product. The admin panel
menu entry needs
webinar_list; the edit screen needswebinar_read. - Tutors (trainers) are moderators in the room. With the default roles they hold only the
-ownpermissions (see Permissions), so the admin menu entry is hidden for them. - Learners see published webinars on the learner site and join from their account once the session has started.
Screens
Section titled “Screens”Webinar list
Section titled “Webinar list”/courses/webinars/list (Courses → Webinars). Columns: ID, name, status, product, duration,
active from, active to and tags. Filters: date range, name and tags. When loading the list fails
with a YouTube error, the panel opens a Generate token dialog: it asks for an e-mail address,
calls POST /api/admin/g-token/generate and opens the Google consent page that reconnects the
YouTube account used for live streams.

The entry can be hidden with the dashboard setting hideInMenu-CoursesWebinars.
Webinar form
Section titled “Webinar form”/courses/webinars/webinar/:webinar (new to create). Tabs:
| Tab | Content |
|---|---|
| Attributes | Name, duration (free text such as 2 hours), status (draft, published, archived), active from, active to, trainers, the “AI Recording Analysis” switch, short description, program (agenda) and description |
| Product | Price and sale settings of the webinar’s product (see Sales) |
| Media | Cover image |
| Tags | Tags used for filtering |
| Branding | Logotype; shown inside the Jitsi room instead of the default logo |
| User submission | Assign users by e-mail, including people without an account yet (assign-without-account) |
The tabs other than Attributes appear after the first save.

How joining works
Section titled “How joining works”- A learner gets access, normally by buying the webinar product; the webinar is then attached to
the user (
webinar_user). Trainers are attached through the form. GET /api/webinars/generate-jitsi/{id}returns the room data. It answers 404 unless the webinar is published and the current time is betweenactive_toandactive_to + duration. When YouTube is configured, the webinar must also have a YouTube stream.- Trainers get moderator rights in the room; the room name is derived from the webinar name.
- The response also carries
yt_url,yt_stream_urlandyt_stream_key, which the trainer uses to broadcast. Trainers start and stop the YouTube broadcast withGET /api/webinars/start-live-stream/{id}andGET /api/webinars/stop-live-stream/{id}.
Video conference configuration (jitsi)
Section titled “Video conference configuration (jitsi)”The jitsi package picks the mode from the configuration:
- JaaS when
app_id,jaas_host,aud,iss,kidandprivate_keyare all set. - Self-hosted Jitsi when
jitsi_host,app_idandsecretare set. The token is signed with HS256; the secret must be at least 32 bytes. - Otherwise no JWT is generated and the room data contains only the domain and room name.
Config key (jitsi.*) |
Environment variable | Default |
|---|---|---|
jitsi_host |
JITSI_HOST |
meet-stage.ulams.app |
app_id |
JITSI_APP_ID |
meet-id |
secret |
JITSI_APP_SECRET |
Test |
jaas_host |
JAAS_HOST |
https://8x8.vc/ |
aud, iss, sub |
JAAS_AUD, JAAS_ISS, JAAS_SUB |
jitsi, chat, empty |
kid, private_key |
JAAS_KEY_ID, JAAS_PRIVATE_KEY |
empty |
recording |
JAAS_RECORDING |
false |
package_status |
none | enabled; any other value makes room generation return “Package is disabled” |
All keys above are also registered as administrable settings, so they can be changed per tenant in
Settings under the jitsi group.
Recording webhook
Section titled “Recording webhook”POST /api/jitsi/recorded-video receives finished recordings, downloads the file and stores it
under jitsi/videos/... on the tenant’s storage disk. Requests are rejected unless one of these is
set:
JITSI_WEBHOOK_SECRET: JaaS signing secret, checked against theX-Jaas-Signatureheader (toleranceJITSI_WEBHOOK_TOLERANCE, default 300 seconds);JITSI_WEBHOOK_TOKEN: shared token for self-hosted Jitsi, sent asAuthorization: Bearer <token>.
Downloads are allowed only over HTTPS from hosts in JITSI_RECORDING_HOSTS (comma-separated,
*.example.com matches subdomains), never from private addresses, only mp4/webm, up to
JITSI_RECORDING_MAX_BYTES (default 2 GB) and JITSI_RECORDING_DOWNLOAD_TIMEOUT (default 600 s).
The endpoint is throttled to 60 requests per minute.
For JaaS, set JITSI_WEBHOOK_SECRET to the endpoint secret shown by the “Reveal secret” button in
the JaaS console (Webhooks). JaaS signs <timestamp>.<raw body> with HMAC-SHA256, base64 encoded, in
X-Jaas-Signature: t=<timestamp>,v1=<signature>; schemes other than v1 are ignored. The
recording link of RECORDING_UPLOADED is a pre-authenticated, 24-hour URL on Oracle object storage
(https://objectstorage.<region>.oraclecloud.com/...mp4), so JaaS deployments set
JITSI_RECORDING_HOSTS=*.oraclecloud.com, or the exact region host if it is known. Both formats follow
the JaaS webhook documentation (checked
2026-10-09). Self-hosted Jitsi leaves the secret empty and uses JITSI_WEBHOOK_TOKEN with its own
recording host.
YouTube
Section titled “YouTube”Live streaming uses the youtube package (Google OAuth client ID, secret and API key, plus a
refresh token obtained through the Google consent flow). When YouTube is configured, saving a webinar
creates or updates an unlisted YouTube broadcast. If it is not configured, webinars run in the
video room only.
API endpoints
Section titled “API endpoints”| Method and path | Purpose |
|---|---|
GET/POST /api/admin/webinars, GET/PUT/DELETE /api/admin/webinars/{id}, POST /api/admin/webinars/{id} |
Admin CRUD (the POST variant accepts multipart uploads) |
GET /api/admin/webinars/{id}/users |
Users attached to a webinar |
GET /api/admin/webinars/users/assignable |
Users that can be assigned as trainers |
GET /api/webinars, GET /api/webinars/{id} |
Public list and detail |
GET /api/webinars/me |
Webinars of the current user |
GET /api/webinars/generate-jitsi/{id} |
Room data for the current user |
GET /api/webinars/start-live-stream/{id}, GET /api/webinars/stop-live-stream/{id} |
Control the YouTube broadcast |
POST /api/pencil-spaces/login |
Direct login link to Pencil Spaces (needs pencil-spaces_login) |
Full list: API endpoints.
Permissions
Section titled “Permissions”| Permission | admin | tutor |
|---|---|---|
webinar_list, webinar_read, webinar_create, webinar_update, webinar_delete |
yes | webinar_create only |
webinar_list-own, webinar_read-own, webinar_update-own, webinar_delete-own |
no | yes |
The -own variants apply to webinars where the user is a trainer. The pencil-spaces_login
permission exists, but the application’s permission seeder does not run the Pencil Spaces seeder,
so no role has it by default. See Permissions.
Events and notifications
Section titled “Events and notifications”| Event | When | E-mail template |
|---|---|---|
WebinarTrainerAssigned |
A trainer is added to a webinar | yes |
WebinarTrainerUnassigned |
A trainer is removed | yes |
ReminderAboutTerm |
One day and one hour before the start, for each attached user | yes |
YtProblem (youtube package) |
The YouTube API call fails | yes |
Reminders come from the scheduled job ReminderAboutWebinarJob: the one-hour reminder runs every
five minutes, the one-day reminder every six hours. Each webinar stores the last reminder sent so a
user is not reminded twice. See Scheduled jobs,
Templates and Events and notifications.
Known limitations
Section titled “Known limitations”- The admin panel only works for users with
webinar_list; tutors cannot manage their own webinars there with the default roles. - The learner site (
front/web) lists webinars read-only; buying and joining happen in the legacy learner app or a custom front. See Learners. - The JaaS webhook signature format still has to be confirmed against the JaaS documentation (open roadmap item).