Skip to main content

Webhooks

Plug-N-Meet can notify your application about various events by sending webhook requests to a specified URL. You can configure this URL in the server settings or provide it when creating a room.

Receiving Webhooks

Webhook requests are sent as HTTP POST requests to the URLs you have configured, either in the server's config.yml file or during room creation. Each webhook event is encoded as JSON and included in the request body.

The request will have the Content-Type header set to application/webhook+json. Make sure your web server is configured to accept payloads with this content type.

Plug-N-Meet uses the same security pattern as LiveKit. To verify that webhook requests originate from Plug-N-Meet, each request includes Authorization and Hash-Token headers containing a signed JWT token. The token includes a SHA256 hash of the payload. For a PHP example, see webhook.php.

Events

You can review the event definitions here.

All webhook events include the following fields:

  • id: A UUID identifying the event
  • createdAt: UNIX timestamp in seconds

Room Created

interface CommonNotifyEvent {
event: 'room_created'
room: Room
}

Room Started

interface CommonNotifyEvent {
event: 'room_started'
room: Room
}

Room Finished

interface CommonNotifyEvent {
event: 'room_finished'
room: Room
}

Participant Joined

interface CommonNotifyEvent {
event: 'participant_joined'
room: Room
participant: ParticipantInfo
}

Participant Left

interface CommonNotifyEvent {
event: 'participant_left'
room: Room
participant: ParticipantInfo
}

Track Published

Only sid, identity, and name are included in the Room and Participant objects.

interface CommonNotifyEvent {
event: 'track_published'
room: Room
participant: ParticipantInfo
track: TrackInfo
}

Track Unpublished

Only sid, identity, and name are included in the Room and Participant objects.

interface CommonNotifyEvent {
event: 'track_unpublished'
room: Room
participant: ParticipantInfo
track: TrackInfo
}

Recording Started

interface CommonNotifyEvent {
event: 'start_recording'
room: Room
recording_info: RecordingInfoEvent
}

Recording Ended

interface CommonNotifyEvent {
event: 'end_recording'
room: Room
recording_info: RecordingInfoEvent
}

Recording Proceeded

interface CommonNotifyEvent {
event: 'recording_proceeded'
room: Room
recording_info: RecordingInfoEvent
}

RTMP Started

interface CommonNotifyEvent {
event: 'start_rtmp'
room: Room
recording_info: RecordingInfoEvent
}

RTMP Ended

interface CommonNotifyEvent {
event: 'end_rtmp'
room: Room
recording_info: RecordingInfoEvent
}

Artifact Created

This single, unified event is triggered whenever a new artifact is generated by the system, such as a transcription or a meeting summary. It replaces multiple older, individual webhooks with a more flexible and scalable model.

For details on the payload structure and possible artifact types, please refer to the protobuf definition.

interface CommonNotifyEvent {
event: 'artifact_created'
room: Room
room_artifact: RoomArtifactWebhookEvent
}

Analytics Proceeded

interface CommonNotifyEvent {
event: 'analytics_proceeded'
room: Room
analytics: AnalyticsEvent
}

Example Webhook Handler

The following pseudo-code demonstrates how to handle incoming webhooks. Your application should inspect the event property to determine what action to take. The artifact_created event is special, as it requires checking a nested type field to understand the payload.

// A mapping from the enum number to a human-readable string.
// See the protobuf definition for the complete, up-to-date list.
const ArtifactType = {
0: 'UNKNOWN_ARTIFACT',
2: 'MEETING_SUMMARY',
4: 'SPEECH_TRANSCRIPTION',
5: 'SPEECH_TRANSCRIPTION_USAGE',
};

function handleWebhook(payload) {
console.log(`Received event: ${payload.event}`);
const roomInfo = payload.room;

switch (payload.event) {
case 'room_started':
console.log(`Room '${roomInfo.name}' has started.`);
break;

case 'participant_joined':
console.log(`User '${payload.participant.name}' joined room '${roomInfo.name}'.`);
break;

case 'artifact_created':
handleArtifactCreated(payload);
break;

case 'room_finished':
console.log(`Room '${roomInfo.name}' has finished.`);
break;

default:
console.log(`Received an unhandled event: ${payload.event}`);
}
}

// A dedicated handler for the 'artifact_created' event
function handleArtifactCreated(payload) {
const artifact = payload.room_artifact;
const artifactType = ArtifactType[artifact.type] || 'UNKNOWN_ARTIFACT';

console.log(`A new artifact of type '${artifactType}' was created.`);

switch (artifactType) {
case 'MEETING_SUMMARY':
case 'SPEECH_TRANSCRIPTION':
// These are file-based artifacts.
if (artifact.metadata && artifact.metadata.file_info) {
const fileInfo = artifact.metadata.file_info;
console.log(`File available at: ${fileInfo.file_path}`);
// Your logic here: e.g., download the file, link it to a recording.
}
break;

case 'SPEECH_TRANSCRIPTION_USAGE':
// This is a usage-based artifact.
if (artifact.metadata && artifact.metadata.duration_usage) {
const usage = artifact.metadata.duration_usage;
console.log(`Transcription usage logged: ${usage.duration_sec} seconds.`);
// Your logic here: e.g., update billing records.
}
break;

default:
console.log(`Received an unhandled artifact type: ${artifactType}`);
}
}

// Example incoming payload for 'artifact_created' (a meeting summary)
const samplePayload = {
"event": "artifact_created",
"id": "EV_abc123",
"createdAt": 1761815000,
"room": {
"sid": "RM_zG9e4bY2pD-1761814595173",
"room_id": "room01",
"name": "Test Room"
},
"room_artifact": {
"type": 2, // Corresponds to MEETING_SUMMARY
"artifact_id": "ART_xyz789",
"metadata": {
"file_info": {
"file_path": "/path/to/summary.txt",
"file_size": 1024,
"mime_type": "text/plain"
}
}
}
};

// You would call your handler with the body of the POST request.
handleWebhook(samplePayload);