loadMissing('room'); $this->dispatch($session->room->organization_id, 'meeting.started', $this->sessionPayload($session)); } public function meetingEnded(Session $session): void { $session->loadMissing('room'); $this->dispatch($session->room->organization_id, 'meeting.ended', $this->sessionPayload($session)); } public function recordingReady(Recording $recording): void { $recording->loadMissing('session.room'); $payload = array_merge($this->sessionPayload($recording->session), [ 'recording' => [ 'uuid' => $recording->uuid, 'status' => $recording->status, 'duration_seconds' => $recording->duration_seconds, 'download_url' => route('meet.recordings.download', $recording), ], ]); $this->dispatch($recording->session->room->organization_id, 'recording.ready', $payload); } public function roomCreated(Room $room): void { $this->dispatch($room->organization_id, 'meeting.scheduled', $this->roomPayload($room)); } public function roomCancelled(Room $room): void { $this->dispatch($room->organization_id, 'meeting.cancelled', $this->roomPayload($room)); } /** @param array $payload */ protected function dispatch(int $organizationId, string $event, array $payload): void { $body = array_merge(['event' => $event, 'timestamp' => now()->toIso8601String()], $payload); WebhookEndpoint::query() ->where('organization_id', $organizationId) ->where('is_active', true) ->get() ->filter(fn (WebhookEndpoint $endpoint) => $endpoint->subscribesTo($event)) ->each(fn (WebhookEndpoint $endpoint) => $this->send($endpoint, $body)); } /** @param array $payload */ protected function send(WebhookEndpoint $endpoint, array $payload): void { $body = json_encode($payload); $headers = ['Content-Type' => 'application/json', 'X-Ladill-Meet-Event' => $payload['event']]; if ($endpoint->secret) { $headers['X-Ladill-Meet-Signature'] = hash_hmac('sha256', $body, $endpoint->secret); } try { Http::timeout(10)->withHeaders($headers)->withBody($body, 'application/json')->post($endpoint->url); } catch (\Throwable $e) { Log::warning('Meet webhook delivery failed', [ 'endpoint_id' => $endpoint->id, 'url' => $endpoint->url, 'error' => $e->getMessage(), ]); } } /** @return array */ protected function roomPayload(Room $room): array { return [ 'room' => [ 'uuid' => $room->uuid, 'title' => $room->title, 'status' => $room->status, 'type' => $room->type, 'scheduled_at' => $room->scheduled_at?->toIso8601String(), 'join_url' => $room->joinUrl(), 'source' => $room->source, ], ]; } /** @return array */ protected function sessionPayload(Session $session): array { return array_merge($this->roomPayload($session->room), [ 'session' => [ 'uuid' => $session->uuid, 'status' => $session->status, 'started_at' => $session->started_at?->toIso8601String(), 'ended_at' => $session->ended_at?->toIso8601String(), 'peak_participants' => $session->peak_participants, ], ]); } }