This hub runs two independent real-time services on a single VPS, both secured with SSL and managed through REST APIs protected by a master bearer token.
Traffic routing
| Request | Path | Handled by |
|---|---|---|
| WebSocket | wss://ws.loveworldlyricsofficial.com/ | Reverb (internal :6001) |
| Reverb API | https://ws.loveworldlyricsofficial.com/api/v1/* | PHP-FPM โ Laravel |
| WebRTC signaling | wss://webrtc.loveworldlyricsofficial.com/ | LiveKit (internal :17880) |
| LiveKit API | https://webrtc.loveworldlyricsofficial.com/api/v1/* | PHP-FPM โ Laravel |
โ ๏ธ Store the Master API Key securely โ it controls all management endpoints. Also saved at /root/.hub-credentials.
Master API Key
Reverb โ Default App
LiveKit โ Default Key
All management API endpoints (except GET /api/v1/health) require the master key as a Bearer token.
curl -s https://ws.loveworldlyricsofficial.com/api/v1/reverb/apps -H "Authorization: Bearer hub_0CRIyqDmqmtXc58VSC5i3bPnBA7UrbZXYO25MfeKk4"Http::withToken($masterKey)->get('https://ws.loveworldlyricsofficial.com/api/v1/reverb/apps');
const res = await fetch('https://ws.loveworldlyricsofficial.com/api/v1/reverb/apps', { headers: { 'Authorization': `Bearer ${masterKey}` } });
| Status | Meaning |
|---|---|
| 401 | No Authorization header |
| 403 | Invalid or revoked key |
| 422 | Validation failed โ check errors field |
| 404 | Route not found |
| Port | Protocol | Service | Exposed | Notes |
|---|---|---|---|---|
| 22 | TCP | SSH | Yes | Server access |
| 80 | TCP | HTTP | Yes | Redirect + ACME challenge |
| 443 | TCP | HTTPS | Yes | Reverb + LiveKit + APIs via server_name |
| 7881 | TCP | LiveKit WebRTC fallback | Yes | Direct TCP |
| 50000โ60000 | UDP | LiveKit media | Yes | Direct, cannot proxy UDP |
| 6001 | TCP | Reverb internal | No | Loopback only |
| 17880 | TCP | LiveKit internal | No | Loopback only |
| 6379 | TCP | Redis | No | Loopback only |
Create multiple keys (one per developer/service), revoke individually. The last active key is protected.
โ ๏ธ Full key shown once only โ store immediately.
curl -s -X POST https://ws.loveworldlyricsofficial.com/api/v1/keys -H "Authorization: Bearer hub_0CRIyqDmqmtXc58VSC5i3bPnBA7UrbZXYO25MfeKk4" -H "Content-Type: application/json" -d '{"name":"Dev Key"}'
Cannot revoke the last active key.
Base: https://ws.loveworldlyricsofficial.com/api/v1 ยท Changes auto-regenerate config/reverb.php and restart Reverb.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | required | App name |
| allowed_origins | array | optional | CORS origins (default: ["*"]) |
Generates new key and secret. Old credentials stop working immediately.
Update name, allowed_origins, or active (boolean).
Removes the app and restarts Reverb. Connected clients will be disconnected.
BROADCAST_CONNECTION=reverb
REVERB_APP_ID=app-58d9b9b4
REVERB_APP_KEY=c6b920a714a2a878a79946541c54500f9ba0b5da
REVERB_APP_SECRET=2938216ff11b6dc00b622915ff8f255f326313f4b046452727449fee0c74f47f
REVERB_HOST=ws.loveworldlyricsofficial.com
REVERB_PORT=443
REVERB_SCHEME=https
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"// npm install laravel-echo pusher-js import Echo from 'laravel-echo'; import Pusher from 'pusher-js'; window.Pusher = Pusher; window.Echo = new Echo({ broadcaster: 'reverb', key: import.meta.env.VITE_REVERB_APP_KEY, wsHost: import.meta.env.VITE_REVERB_HOST, wsPort: import.meta.env.VITE_REVERB_PORT, wssPort: import.meta.env.VITE_REVERB_PORT, forceTLS: true, enabledTransports: ['ws','wss'], disableStats: true, });
// npm install pusher-js new Pusher('c6b920a714a2a878a79946541c54500f9ba0b5da', { wsHost: 'ws.loveworldlyricsofficial.com', wsPort: 443, wssPort: 443, forceTLS: true, enabledTransports: ['ws','wss'], cluster: 'mt1', disableStats: true, });
# composer require pusher/pusher-php-server $p = new Pusher\Pusher('c6b920a714a2a878a79946541c54500f9ba0b5da','2938216ff11b6dc00b622915ff8f255f326313f4b046452727449fee0c74f47f','app-58d9b9b4', ['host'=>'ws.loveworldlyricsofficial.com','port'=>443,'scheme'=>'https','useTLS'=>true]); $p->trigger('my-channel','my-event',['msg'=>'Hello!']);
Base: https://webrtc.loveworldlyricsofficial.com/api/v1
| Field | Type | Description |
|---|---|---|
| name | string | Label for this key (optional) |
Pass {"active": false} to disable without deleting.
Tokens signed with this key will stop working immediately.
โน๏ธ Always generate tokens server-side. Never expose your API Secret in browser code.
| Field | Type | Required | Description |
|---|---|---|---|
| room | string | required | Room name |
| identity | string | required | Unique participant ID |
| name | string | optional | Display name |
| ttl | integer | optional | Expiry seconds (default: 3600) |
| can_publish | boolean | optional | Can publish tracks (default: true) |
| can_subscribe | boolean | optional | Can subscribe (default: true) |
curl -s -X POST https://webrtc.loveworldlyricsofficial.com/api/v1/livekit/tokens -H "Authorization: Bearer hub_0CRIyqDmqmtXc58VSC5i3bPnBA7UrbZXYO25MfeKk4" -H "Content-Type: application/json" -d '{"room":"my-room","identity":"user-42","name":"Alice"}'
Fields: name (required), empty_timeout (seconds, default 300), max_participants (default 100).
Removes the participant from the room immediately.
Closes the room and disconnects all participants.
Backend: call POST /api/v1/livekit/tokens โ get JWT
Frontend: receive token, connect to wss://webrtc.loveworldlyricsofficial.com
LiveKit handles all WebRTC negotiation and media routing automatically
// npm install livekit-client import { Room, RoomEvent, Track } from 'livekit-client'; const { data: { token } } = await fetch('/api/livekit-token', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({room:'my-room', identity:'user-1'}) }).then(r=>r.json()); const room = new Room({adaptiveStream:true, dynacast:true}); room.on(RoomEvent.TrackSubscribed, track => { if(track.kind===Track.Kind.Video||track.kind===Track.Kind.Audio) document.getElementById('video').appendChild(track.attach()); }); await room.connect('wss://webrtc.loveworldlyricsofficial.com', token); await room.localParticipant.enableCameraAndMicrophone();
// npm install @livekit/components-react livekit-client import { LiveKitRoom, VideoConference } from '@livekit/components-react'; import '@livekit/components-styles'; export default function Meeting({token}) { return <LiveKitRoom serverUrl="wss://webrtc.loveworldlyricsofficial.com" token={token} video audio> <VideoConference/> </LiveKitRoom>; }
// npm install livekit-client import { Room } from 'livekit-client'; import { onMounted, onUnmounted, ref } from 'vue'; export function useLiveKit(roomName, userId) { const room = ref(null); onMounted(async () => { const {data} = await fetch('/api/livekit-token',{ method:'POST',headers:{'Content-Type':'application/json'}, body:JSON.stringify({room:roomName,identity:userId}) }).then(r=>r.json()); room.value = new Room(); await room.value.connect('wss://webrtc.loveworldlyricsofficial.com', data.token); await room.value.localParticipant.enableCameraAndMicrophone(); }); onUnmounted(() => room.value?.disconnect()); return { room }; }
Client project .env
HUB_MASTER_KEY=hub_0CRIyqDmqmtXc58VSC5i3bPnBA7UrbZXYO25MfeKk4 HUB_REVERB_API=https://ws.loveworldlyricsofficial.com/api/v1 HUB_LIVEKIT_API=https://webrtc.loveworldlyricsofficial.com/api/v1
Broadcast an event
// app/Events/OrderShipped.php class OrderShipped implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; public function __construct(public Order $order) {} public function broadcastOn(): array { return [new Channel('orders')]; } } broadcast(new OrderShipped($order));
LiveKit token endpoint
$res = Http::withToken(config('services.hub.key')) ->post(config('services.hub.lk_api').'/livekit/tokens', [ 'room' => $request->room, 'identity' => (string) auth()->id(), 'name' => auth()->user()->name, ]); return response()->json($res->json('data'));
// hub.js const KEY = process.env.HUB_MASTER_KEY; const RV = 'https://ws.loveworldlyricsofficial.com/api/v1'; const LK = 'https://webrtc.loveworldlyricsofficial.com/api/v1'; const call = (m,b,p,d) => fetch(b+p,{method:m, headers:{'Authorization':`Bearer ${KEY}`,'Content-Type':'application/json'}, body:d?JSON.stringify(d):undefined}).then(r=>r.json()); export const listApps = () => call('GET', RV,'/reverb/apps'); export const createApp = b => call('POST', RV,'/reverb/apps',b); export const rotateApp = id => call('POST', RV,`/reverb/apps/${id}/rotate`); export const genToken = b => call('POST', LK,'/livekit/tokens',b); export const listRooms = () => call('GET', LK,'/livekit/rooms');
# pip install requests import requests H = {"Authorization": "Bearer hub_0CRIyqDmqmtXc58VSC5i3bPnBA7UrbZXYO25MfeKk4"} RV = "https://ws.loveworldlyricsofficial.com/api/v1" LK = "https://webrtc.loveworldlyricsofficial.com/api/v1" apps = requests.get(f"{RV}/reverb/apps", headers=H).json() token = requests.post(f"{LK}/livekit/tokens", headers=H, json={"room":"my-room","identity":"user-1"}).json()["data"]["token"] rooms = requests.get(f"{LK}/livekit/rooms", headers=H).json()
Service commands
supervisorctl status systemctl status nginx redis-server php8.3-fpm curl -s https://ws.loveworldlyricsofficial.com/api/v1/health
supervisorctl restart reverb # disconnects WS clients supervisorctl restart livekit # disconnects RTC participants systemctl reload nginx # zero-downtime config reload
tail -f /var/log/reverb.log tail -f /var/log/livekit.log journalctl -u nginx -f journalctl -u php8.3-fpm -f
sqlite3 /var/www/reverb-hub/database/database.sqlite # Inside SQLite: .tables SELECT * FROM hub_master_keys; SELECT app_id, name, key FROM reverb_apps; SELECT api_key, name, active FROM livekit_keys; # Backup: cp /var/www/reverb-hub/database/database.sqlite /root/hub-backup-$(date +%Y%m%d).sqlite
openssl x509 -enddate -noout -in /etc/letsencrypt/live/ws.loveworldlyricsofficial.com/cert.pem openssl x509 -enddate -noout -in /etc/letsencrypt/live/webrtc.loveworldlyricsofficial.com/cert.pem certbot renew --dry-run # test auto-renewal sudo bash /root/retry-ssl.sh # get real certs if self-signed
File locations
| Path | Purpose |
|---|---|
| /var/www/reverb-hub/ | Laravel hub app |
| /var/www/reverb-hub/.env | Laravel config |
| /var/www/reverb-hub/config/reverb.php | Auto-generated Reverb apps |
| /var/www/reverb-hub/database/database.sqlite | All keys and apps |
| /etc/livekit/config.yaml | LiveKit config |
| /root/.hub-credentials | Credentials backup |
| /var/log/reverb.log | Reverb logs |
| /var/log/livekit.log | LiveKit logs |