# PlugNMeet Webhook Integration ## Overview This document describes the webhook integration between PlugNMeet and the Django backend to handle live session events. ## Webhook Endpoint **URL:** `https://habibmeet.nwhco.ir/api/course/plugnmeet/webhook/` **Method:** `POST` **Content-Type:** `application/webhook+json` ## Authentication The webhook endpoint is secured using SHA256 HMAC signature verification. ### Headers - `Hash-Token`: SHA256 HMAC signature of the request body using `PLUGNMEET_API_SECRET` - `Content-Type`: `application/webhook+json` - `Authorization`: JWT token (optional, for additional verification) ### Signature Verification ```python import hmac import hashlib # Calculate signature signature = hmac.new( PLUGNMEET_API_SECRET.encode('utf-8'), request_body, hashlib.sha256 ).hexdigest() # Compare with Hash-Token header is_valid = hmac.compare_digest(hash_token_header, signature) ``` ## Supported Events ### 1. room_finished Triggered when a live session room is closed. **Action:** Closes the `CourseLiveSession` and marks all active `LiveSessionUser` entries as offline. **Payload:** ```json { "event": "room_finished", "id": "550e8400-e29b-41d4-a716-446655440000", "createdAt": 1697500800, "room": { "sid": "room-123456", "identity": "algebra-1402", "name": "کلاس جبر", "maxParticipants": 100, "creationTime": 1697497200, "metadata": "{}", "numParticipants": 0, "duration": 3600 } } ``` **Response:** ```json { "status": "ok", "message": "Room finished", "session_id": 123, "users_disconnected": 5 } ``` ### 2. participant_joined Triggered when a user joins the live session. **Action:** Creates a new `LiveSessionUser` entry or reactivates an existing offline entry. **Payload:** ```json { "event": "participant_joined", "id": "660e8400-e29b-41d4-a716-446655440001", "createdAt": 1697497300, "room": { "sid": "room-123456", "identity": "algebra-1402", "name": "کلاس جبر" }, "participant": { "sid": "participant-user-27", "identity": "27", "state": "ACTIVE", "name": "دانشجو نمونه", "metadata": "{\"is_admin\": false}", "permission": { "canPublish": true, "canPublishData": true, "canSubscribe": true }, "tracks": [], "joinedAt": 1697497300 } } ``` **Response:** ```json { "status": "ok", "message": "Participant joined", "session_user_id": 456, "created": true } ``` ### 3. participant_left Triggered when a user leaves the live session. **Action:** Marks the user's `LiveSessionUser` entry as offline and sets `exited_at` timestamp. **Payload:** ```json { "event": "participant_left", "id": "770e8400-e29b-41d4-a716-446655440002", "createdAt": 1697499000, "room": { "sid": "room-123456", "identity": "algebra-1402", "name": "کلاس جبر" }, "participant": { "sid": "participant-user-27", "identity": "27", "state": "DISCONNECTED", "name": "دانشجو نمونه", "metadata": "{\"is_admin\": false}", "permission": { "canPublish": true, "canPublishData": true, "canSubscribe": true }, "tracks": [], "joinedAt": 1697497300, "duration": 1800 } } ``` **Response:** ```json { "status": "ok", "message": "Participant left", "updated": true } ``` ### 4. end_recording Triggered when a recording finishes. **Action:** 1. Fetches recording info from PlugNMeet API 2. Gets download token 3. Downloads the recording file 4. Saves to `LiveSessionRecording` model 5. Generates thumbnail for video recordings (requires `ffmpeg`) **Payload:** ```json { "event": "end_recording", "id": "880e8400-e29b-41d4-a716-446655440003", "createdAt": 1697500800, "room": { "sid": "room-123456", "identity": "algebra-1402", "name": "کلاس جبر" }, "recording_info": { "recordingId": "rec-123456", "roomId": "algebra-1402", "recordingType": "COMPOSITE", "fileName": "algebra-1402-20231016.mp4", "duration": 3600, "status": "FINISHED" } } ``` **Response (Success):** ```json { "status": "ok", "message": "Recording downloaded and saved successfully", "recording_id": 123, "file_name": "algebra-1402-20231016.mp4", "file_size": 524288000, "thumbnail_generated": true } ``` **Response (Error):** ```json { "status": "error", "message": "Failed to get recording info", "error": "Recording not found" } ``` **Requirements:** - `ffmpeg` must be installed for thumbnail generation - Sufficient disk space for recording files - Write permissions on media directories ## Configuration Add these settings to your Django settings file: ```python # PlugNMeet Integration PLUGNMEET_SERVER_URL = "https://plugnmeet.example.com" PLUGNMEET_API_KEY = "your-api-key" PLUGNMEET_API_SECRET = "your-api-secret" PLUGNMEET_TIMEOUT = 10.0 ``` ## PlugNMeet Configuration Configure the webhook URL in your PlugNMeet server settings: ```yaml webhooks: - url: "https://habibmeet.nwhco.ir/api/course/plugnmeet/webhook/" events: - room_finished - participant_joined - participant_left - end_recording ``` ## Error Handling The webhook endpoint returns appropriate HTTP status codes: - `200 OK`: Event processed successfully - `400 Bad Request`: Invalid payload or missing required fields - `403 Forbidden`: Invalid webhook signature - `500 Internal Server Error`: Server error during processing All errors are logged with detailed information for debugging. ## Testing To test the webhook locally, you can use curl: ```bash # Calculate signature SECRET="your-api-secret" PAYLOAD='{"event":"room_finished","room":{"identity":"test-room"}}' SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" -hex | cut -d' ' -f2) # Send webhook request curl -X POST https://habibmeet.nwhco.ir/api/course/plugnmeet/webhook/ \ -H "Content-Type: application/webhook+json" \ -H "Hash-Token: $SIGNATURE" \ -d "$PAYLOAD" ``` ## Migration from Polling Previously, the system used a polling approach where it would check if a room was still active using the `is_room_active` API call. This has been deprecated in favor of the webhook approach: **Old (Deprecated):** ```python # This code has been commented out def _sync_room_status_with_plugnmeet(self, course: Course): client = PlugNMeetClient() response = client.is_room_active(active_session.room_id) if not response.get('isActive', False): self._close_live_session(active_session) ``` **New (Webhook-based):** ```python # Room status is automatically updated via webhooks # No polling required ``` ## Benefits 1. **Real-time updates**: No polling delay, events are processed immediately 2. **Reduced server load**: No need for periodic API calls to check room status 3. **Accurate tracking**: Precise participant join/leave timestamps 4. **Scalability**: Webhook approach scales better than polling 5. **Lower latency**: Users see status updates immediately 6. **Automatic recording management**: Recordings are automatically downloaded and saved when ready ## Recording Management The webhook automatically handles recording downloads when the `end_recording` event is received: ### Process Flow 1. **Webhook receives end_recording event** 2. **Fetch recording info** from PlugNMeet API (`/auth/recording/recordingInfo`) 3. **Get download token** (`/auth/recording/getDownloadToken`) 4. **Download file** to temporary location 5. **Determine recording type** (video/voice) based on file extension 6. **Create database record** (`LiveSessionRecording`) 7. **Generate thumbnail** (for video files using ffmpeg) 8. **Clean up** temporary files ### File Storage - **Location**: Configured by `MEDIA_ROOT` in Django settings - **Upload path**: `recorded_sessions/` (from model definition) - **Thumbnails**: `recording_thumbnails/` (for video recordings) ### Thumbnail Generation For video recordings, a thumbnail is automatically generated using `ffmpeg`: - Extracts frame at 1 second - Scaled to width 640px (maintains aspect ratio) - High quality JPEG (quality level 2) - Saved to `recording.thumbnail` field **Note**: `ffmpeg` must be installed on the server for thumbnail generation to work. ### Error Handling The recording download process includes comprehensive error handling: - Missing recording: Returns appropriate error message - Download failures: Logs error and returns failure status - Thumbnail generation: Non-critical, failures are logged but don't stop the process - Cleanup: Temporary files are always cleaned up, even on errors ## Logging All webhook events are logged with detailed information: ``` [PlugNMeet Webhook] Received webhook request [PlugNMeet Webhook] Processing event=room_finished [PlugNMeet Webhook] Session closed - session_id=123 room_id=algebra-1402 [PlugNMeet Webhook] User sessions closed - session_id=123 count=5 [PlugNMeet Webhook] Event processed successfully - event=room_finished ``` Check Django logs for webhook activity and debugging information.