# PlugNMeet Webhook Integration - Quick Setup Guide ## Overview This project implements automatic webhook integration with PlugNMeet to handle live session events in real-time. ## Features ✅ **Room Management** - Automatically close sessions when room ends - Real-time session status updates ✅ **Participant Tracking** - Track when users join/leave sessions - Maintain accurate online status ✅ **Recording Management** - Automatically download completed recordings - Generate video thumbnails - Save to database with metadata ## Prerequisites ### Required Software ```bash # Install FFmpeg (required for video thumbnail generation) sudo apt-get update sudo apt-get install ffmpeg # Verify installation ffmpeg -version ``` ### Django Settings Ensure these settings are configured in your `settings.py`: ```python # PlugNMeet Configuration PLUGNMEET_SERVER_URL = "https://your-plugnmeet-server.com" PLUGNMEET_API_KEY = "your-api-key" PLUGNMEET_API_SECRET = "your-api-secret" PLUGNMEET_TIMEOUT = 10.0 # Media files (for recordings) MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/' ``` ## PlugNMeet Server Configuration Configure webhook in your PlugNMeet server settings: ```yaml # plugnmeet config.yaml webhooks: - url: "https://your-django-backend.com/api/course/plugnmeet/webhook/" events: - room_finished - participant_joined - participant_left - end_recording ``` ## Webhook Endpoint **URL:** `https://your-domain.com/api/course/plugnmeet/webhook/` **Method:** `POST` **Security:** HMAC SHA256 signature verification ## Events Handled ### 1. room_finished - Closes the live session - Marks all participants as offline - Sets `ended_at` timestamp ### 2. participant_joined - Creates `LiveSessionUser` entry - Sets user as online - Records join timestamp ### 3. participant_left - Updates `LiveSessionUser` entry - Sets user as offline - Records exit timestamp ### 4. end_recording - Fetches recording from PlugNMeet - Downloads recording file - Saves to `LiveSessionRecording` model - Generates video thumbnail (if applicable) ## Testing ### Using the Test Script ```bash # Test room_finished event python scripts/test_webhook.py room_finished # Test participant_joined event python scripts/test_webhook.py participant_joined # Test participant_left event python scripts/test_webhook.py participant_left # Test end_recording event python scripts/test_webhook.py end_recording # Dry run (show payload without sending) python scripts/test_webhook.py room_finished --dry-run ``` ### Manual Testing with cURL ```bash #!/bin/bash # Configuration SECRET="your-api-secret" URL="https://your-domain.com/api/course/plugnmeet/webhook/" # Sample payload PAYLOAD='{ "event": "room_finished", "room": { "identity": "test-room-20240101120000" } }' # Calculate signature SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" -hex | cut -d' ' -f2) # Send request curl -X POST "$URL" \ -H "Content-Type: application/webhook+json" \ -H "Hash-Token: $SIGNATURE" \ -d "$PAYLOAD" ``` ## Monitoring ### Check Logs ```bash # Django logs tail -f logs/django.log | grep "PlugNMeet Webhook" # Specific events tail -f logs/django.log | grep "end_recording" tail -f logs/django.log | grep "participant_joined" ``` ### Log Messages ``` [PlugNMeet Webhook] Received webhook request [PlugNMeet Webhook] Processing event=room_finished [PlugNMeet Webhook] Session closed - session_id=123 room_id=test-room [PlugNMeet Webhook] User sessions closed - session_id=123 count=5 [PlugNMeet Webhook] Event processed successfully - event=room_finished ``` ### Recording Download Logs ``` [PlugNMeet Webhook] end_recording - room_id=test-room recording_id=rec_123 [PlugNMeet Webhook] Fetching recording info - recording_id=rec_123 [PlugNMeet Webhook] Getting download token - recording_id=rec_123 [PlugNMeet Webhook] Downloading recording file - recording_id=rec_123 [PlugNMeet Webhook] File downloaded - size=524288000 bytes [PlugNMeet Webhook] Recording saved - recording_id=456 file=recording.mp4 [PlugNMeet Webhook] Thumbnail generated - recording_id=456 ``` ## Database Models ### CourseLiveSession ```python { 'id': 123, 'course': Course instance, 'room_id': 'test-room-20240101120000', 'subject': 'Test Session', 'started_at': datetime, 'ended_at': datetime, # Set by webhook } ``` ### LiveSessionUser ```python { 'id': 456, 'session': CourseLiveSession instance, 'user': User instance, 'role': 'participant' or 'moderator', 'entered_at': datetime, # Set by webhook 'exited_at': datetime, # Set by webhook 'is_online': True/False, # Updated by webhook } ``` ### LiveSessionRecording ```python { 'id': 789, 'session': CourseLiveSession instance, 'title': 'Test Session - Recording', 'file': FileField, # Downloaded by webhook 'file_time': DurationField, 'recording_type': 'video' or 'voice', 'thumbnail': ImageField, # Generated by webhook 'is_active': True, } ``` ## Troubleshooting ### Webhook Not Receiving Events 1. Check PlugNMeet server configuration 2. Verify webhook URL is accessible from PlugNMeet server 3. Check firewall rules 4. Review PlugNMeet server logs ### Signature Verification Failed 1. Ensure `PLUGNMEET_API_SECRET` matches PlugNMeet config 2. Check for extra whitespace in settings 3. Verify request is coming from PlugNMeet server ### Recording Download Failed 1. Check PlugNMeet server is accessible 2. Verify recording exists: `POST /auth/recording/recordingInfo` 3. Check disk space 4. Review media directory permissions ### Thumbnail Generation Failed 1. Verify ffmpeg is installed: `ffmpeg -version` 2. Check ffmpeg has permissions to read/write temp files 3. Review video file format (mp4, webm, mkv supported) 4. Check server resources (CPU, memory) ### File Upload Errors ```python # Check media directory permissions ls -la media/ chmod -R 755 media/ # Check Django settings python manage.py shell >>> from django.conf import settings >>> print(settings.MEDIA_ROOT) >>> print(settings.MEDIA_URL) ``` ## Performance Considerations ### Disk Space - Monitor disk space for recordings - Implement cleanup policy for old recordings - Consider using external storage (S3, MinIO) ### Processing Time - Large recordings may take time to download - Thumbnail generation adds 1-3 seconds per video - Consider async processing for large files (Celery) ### Concurrent Webhooks - Django handles webhooks synchronously by default - For high-traffic scenarios, consider: - Queue system (Celery, RQ) - Async views (Django 4.1+) - Horizontal scaling ## Migration from Polling The old polling approach has been deprecated and commented out: ```python # OLD (Deprecated) - in apps/course/views/course.py # def _sync_room_status_with_plugnmeet(self, course: Course): # client = PlugNMeetClient() # response = client.is_room_active(active_session.room_id) # ... # NEW (Webhook-based) # Room status is automatically updated via webhooks # No polling required ``` ## Security Best Practices 1. ✅ **Signature Verification**: Always enabled (HMAC SHA256) 2. ✅ **HTTPS Only**: Webhook endpoint requires HTTPS 3. ✅ **IP Whitelist**: Consider restricting to PlugNMeet server IP 4. ✅ **Rate Limiting**: Implement rate limiting on webhook endpoint 5. ✅ **Input Validation**: All webhook payloads are validated 6. ✅ **Error Handling**: Comprehensive error handling and logging ## Support For issues or questions: 1. Check logs: `logs/django.log` 2. Review documentation: `docs/plugnmeet_webhook.md` 3. Test with script: `scripts/test_webhook.py` 4. Check PlugNMeet docs: https://www.plugnmeet.org/docs ## References - [PlugNMeet Webhook Documentation](docs/plugnmeet_webhook.md) - [PlugNMeet API Documentation](docs/plugnmeet_api.md) - [Test Script](scripts/test_webhook.py) - [Webhook Implementation](apps/course/views/webhook.py)