You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

8.9 KiB

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

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:

{
  "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:

{
  "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:

{
  "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:

{
  "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:

{
  "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:

{
  "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:

{
  "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):

{
  "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):

{
  "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:

# 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:

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:

# 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):

# 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):

# 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.