Setup guide

How it works

World Space Meet has two parts. This app (accounts, rooms, dashboard, API) runs on your Hostinger Node.js hosting. The live video and audio run on a media service called LiveKit, because shared web hosting cannot carry real-time video traffic. LiveKit Cloud has a free plan, so you can launch without paying for video.

Get your free video keys

  1. Create a free account at cloud.livekit.io. No card is needed for the Build plan.
  2. Create a project. Copy its WebSocket URL. It looks like wss://your-project.livekit.cloud.
  3. Open the project's Settings, then Keys, and create a key. Copy the API key and the API secret.
  4. In World Space Meet, sign in as the owner, open Settings on the dashboard, paste the three values, press Save, then Test connection.

The free Build plan includes about 5,000 participant-minutes a month and 100 people connected at once (at the time of writing; check LiveKit's pricing page). A one-hour call with 5 people uses 300 minutes. Past the limit, new joins fail until the month resets or you upgrade.

Deploy on Hostinger Business

  1. Use the ZIP file that came with this project: world-space-meet-hostinger.zip. It has no node_modules, which is what Hostinger expects.
  2. In hPanel open Websites, then Add Website, then Node.js web app, then Upload your files, and choose the ZIP.
  3. Confirm the settings: Node.js 20 or newer, install command npm install, start command npm start.
  4. Add environment variables (optional but recommended): SIGNUPS=closed after you create the owner account, and DATA_FILE if you want the data file in a specific folder.
  5. Press Deploy, then connect your domain from the site's dashboard. HTTPS is required for camera access, and Hostinger provides the certificate.

Your data (accounts and rooms) is stored in one JSON file, data/wsmeet-data.json. Back it up before redeploying. For a larger business, move to the MySQL database that comes with your plan.

First run

  1. Open your site and create an account. The first account becomes the owner and gets the Pro plan.
  2. Add the LiveKit keys in Settings (see above).
  3. Start a room. Open the link in a private window to join as a guest and test video.
  4. Every account belongs to a company or organization, named at sign-up. All applications and API keys created afterward live under that company. Set SIGNUPS=closed if you do not want strangers creating accounts, or leave it open to run it as a SaaS. New accounts start on the Free plan (20 rooms a month). Change any account to Pro in Settings.

Using rooms

API and embed

Create a room from your own server with the API key from the dashboard:

curl -X POST /v1/meetings \
  -H "x-api-key: YOUR_KEY" -H "content-type: application/json" \
  -d '{"template":"interview","title":"Round 2"}'

The reply has id and hostKey. Send guests to /?room=ID&join=1. Give the host a link after storing hostKey in their session, or have them sign in and press Open.

Embed a room inside another site:

<iframe src="/?room=ID&join=1&embed=1&theme=light"
  allow="camera; microphone; display-capture; fullscreen"
  style="width:100%;height:600px;border:0"></iframe>

Applications and branding for 3rd parties

The Applications panel on the dashboard is where you give a 3rd party (a client, a partner, or your own other product) its own way into World Space Meet. Each application gets:

To use an application from outside code, send its key the same way as a personal key:

curl -X POST /v1/meetings   -H "x-api-key: nxa_YOUR_APP_KEY" -H "content-type: application/json"   -d '{"template":"meeting","title":"Branded room"}'

Companies and their applications are kept separate from each other: one company can never see or edit another company's applications, API keys, or data.

Webhooks

From the dashboard, add a URL and pick which events to send: meeting.created, participant.joined, meeting.ended. Each account manages its own webhooks (up to 10), and each one gets its own signing secret, shown once when you create it.

Every delivery is a POST with a JSON body and an x-wsmeet-signature header, which is an HMAC-SHA256 of the raw body using your webhook's secret. Verify it before trusting the payload:

const crypto = require('crypto');
const expected = crypto.createHmac('sha256', YOUR_WEBHOOK_SECRET).update(rawBody).digest('hex');
if (expected !== req.headers['x-wsmeet-signature']) return res.status(401).end();

Use Send test on the dashboard to confirm your endpoint is reachable before relying on it.

World Space ID (single sign-on)

World Space Meet can let people sign in with World Space ID — your organization's own OAuth 2.0 / OIDC identity provider — instead of, or alongside, an email and password. This uses the standard Authorization Code flow with PKCE, the same pattern World Space ID's other client apps use.

  1. In World Space ID's admin area, register a new OAuth client app. Set its redirect URI to https://your-domain.com/auth/wsid/callback. Copy the client ID and client secret it gives you.
  2. In the dashboard's owner Settings, open World Space ID sign-in, paste your World Space ID base URL (e.g. https://id.yourcompany.com), the client ID and client secret, then press Save and Test connection.
  3. A "Sign in with World Space ID" button now appears on the sign-in page automatically. The first person who signs in that way becomes the owner if no account exists yet; everyone else is created as a regular member of their World Space ID organization.

Behind the scenes: /auth/wsid/start generates a PKCE verifier/challenge pair and redirects to World Space ID's authorize endpoint; /auth/wsid/callback exchanges the returned code for tokens (verifying the PKCE verifier), fetches the user's profile from the userinfo endpoint, and creates or updates a local account matched by the World Space ID sub (falling back to matching by email). A normal session cookie is issued either way, so the rest of the app doesn't need to know how someone signed in.

Live Monitor

The owner's dashboard includes a Live Monitor panel: a running log of sign-ins (password or World Space ID) and admin actions — who, when, from which IP, and the device (mobile or desktop, OS, browser) detected from their browser. It keeps the most recent 500 events and needs no setup.

Updates, version and rollback

The dashboard's Updates & version panel shows the running version and lets the owner apply an update without using Hostinger's File Manager: build a release .zip containing only public/*.html, server.js, package.json and/or README.md, and upload it there. The server backs up every file it's about to replace into data/backups/<timestamp>/ first, rejects any filename outside that allowed set (no path traversal, no new dependencies), then extracts the new files in place. Static page changes apply immediately; if server.js or package.json changed, restart the Node.js app from hPanel to pick them up.

Every one of those backups stays listed in the Backups & rollback card below it. Press "Restore this version" on any of them to put those exact files back — the files it's about to overwrite are backed up first too, so a restore is itself undoable.

Setup page and super admin

On a brand-new install with no accounts yet, opening the site sends you to /setup.html — a guided installer in the same spirit as World Space ID's own setup.php. It checks the Node.js version, confirms the data folder is writable, and creates the first account, which becomes the super admin (role admin, Pro plan, full access to every owner-only panel). After that first account exists, new visitors go straight to the sign-in page instead.

Database (optional MySQL)

World Space Meet keeps accounts, rooms and settings in a single JSON file by default — nothing to install, nothing that can refuse a connection. If you'd rather use your own MySQL database (Hostinger's databases work fine), save its host, port, user, password and database name in Dashboard > Settings > Database, press Test connection (a real SELECT 1 against it), then press Switch storage to MySQL. Everything currently in the local file is copied over immediately, and every save from then on writes to both the local file and MySQL, with MySQL as the source of truth read back at startup.

This is a snapshot sync, not a relational schema — one table (wsmeet_store) holding the whole app's data as a single JSON column, updated on every write. It trades normalized SQL for a safe, mistake-proof migration, and it solves the one thing that actually matters on most hosts: your accounts and rooms surviving a redeploy that wipes the filesystem.

Important: the database connection itself is normally saved inside that same local file, so on a host where redeploys wipe the filesystem entirely (not just update it), the server would have nothing left telling it where to reconnect. Avoid that by setting DB_HOST, DB_USER, DB_PASSWORD, DB_NAME and optionally DB_PORT as environment variables in hPanel (Node.js app > Settings) once you've switched storage to MySQL — env vars survive a redeploy even when the filesystem doesn't, and the server falls back to them automatically whenever the local file has no working database connection of its own.

Video & API providers

The Video & API panel lets the owner configure more than one WebRTC backend and switch the active one at any time — new rooms use whichever is active when they're created:

Switching providers doesn't affect rooms already in progress, only rooms created after the switch. Host controls that need server-side coordination — "Mute all" and "Remove participant" — only work with LiveKit; Jitsi, Daily.co and Twilio don't expose an equivalent through this API, so those buttons stay hidden for rooms on those providers. "End for all" works everywhere.

Recording

Hosts can record LiveKit rooms (meeting, interview, webinar, instant — not Jitsi or Daily.co, which have their own recording inside their own call UI) with the Start recording button in the room's controls. World Space Meet doesn't encode video itself: LiveKit's Egress service composites the room and uploads the finished file straight to an S3-compatible bucket, so recording needs two things configured in Dashboard > Settings > Recording:

  1. An S3-compatible bucket — AWS S3, Cloudflare R2, Backblaze B2, MinIO, or similar — with its endpoint, region, access key and secret.
  2. LiveKit Egress itself, which only comes with LiveKit Cloud or a self-hosted LiveKit deployment that includes the Egress worker; the plain --dev LiveKit server used for local testing doesn't run it.

Point LiveKit's webhook (in your LiveKit project settings) at the URL shown on the Recording card (/webhooks/livekit). It's signed with your LiveKit API key and secret and verified the same way — when a recording finishes, that webhook is how the file's location reaches World Space Meet; there's no polling. A recording's status and file location are available at GET /v1/meetings/:id/recordings.

Live captions

The "Captions" button in LiveKit and Twilio rooms (meeting, interview, webinar, instant — not Jitsi or Daily.co, which have their own captions inside their own call UI) turns on speech-to-text using each participant's own browser, not a server-side service: no API key, no extra cost, works in Chrome and Edge (browsers without the Web Speech API simply don't show the button). Each person captions their own microphone; the text shows as an overlay for everyone in the room and is saved to that room's transcript, the same place typed chat goes.

AI meeting summaries

There's no server-side speech-to-text pipeline — this summarizes whatever ended up in the room's transcript: typed chat messages, and spoken lines captured by live captions if anyone turned them on. Once the meeting ends, anyone can turn the transcript into brief meeting notes (a summary paragraph plus bullet points) from the "Past meetings & AI summaries" card on the dashboard's Overview page.

To turn it on, add an AI provider in Dashboard > Settings > AI meeting summaries: a base URL, a model name, and an API key. It works with any OpenAI-compatible chat completions API — OpenAI itself, Groq, OpenRouter, or a local server like Ollama (point the base URL at its OpenAI-compatible endpoint). Summaries are generated on request, not automatically, and are cached on the room once made.

Billing (Stripe Checkout)

Anyone can upgrade themselves from Free (20 rooms/month) to Pro (2,000 rooms/month) without the owner doing anything manually, using Stripe Checkout — card details are entered on Stripe's own hosted page and never touch this server.

  1. In your Stripe dashboard, copy a secret key (Developers > API keys).
  2. Create a webhook endpoint pointed at https://your-domain.com/api/billing/webhook, subscribed to the checkout.session.completed and customer.subscription.deleted events, and copy its signing secret.
  3. Paste both, plus the monthly price in cents, into Dashboard > Settings > Billing, then press Test connection.

From then on, a Free account sees an "Upgrade to Pro" banner on their Overview page. It opens a Stripe Checkout session (a $-per-month subscription) with that account's id attached as the client_reference_id; when Stripe confirms payment, the signed webhook flips their plan to Pro, and a cancelled subscription flips it back to Free. The dashboard also double-checks right after a successful checkout redirect, so the upgrade shows immediately even if the webhook is a few seconds behind. Use a sk_test_ key and Stripe's test card numbers to try the whole flow before going live.

Accounts and passwords

Forgot your password? Use the link on the sign-in page. This install has no outgoing email set up, so instead of emailing a link, it shows the link on screen right after you request it. Treat that link like a password: anyone with it can reset the account. It expires in one hour and works once.

New accounts also get an email verification link the same way — shown on screen, not mailed. An unverified account still works normally; the Overview page just shows a banner with a "Show verification link" button until it's confirmed.

Team

The account that creates an organization is its owner. From Dashboard > Team, the owner can invite teammates by email; each invite is a one-time link (expires in 7 days) handed back the same way the password reset and verification links are — copy it and send it yourself. Anyone who accepts joins the same organization, sharing its rooms, applications and plan, but keeps their own login, API key, and webhook settings. Members can see the team list but can't invite or remove anyone; only the owner (or the site super admin) can.

Troubleshooting