WebhookConfig::latest()->get(), 'recentLogs' => \Dashboard\Unifi\Models\WebhookLog::with('config:id,name') ->latest('fired_at')->take(50)->get(), 'eventTypes' => WebhookCheckService::EVENTS, 'templateVars' => WebhookCheckService::TEMPLATE_VARS, 'defaultTemplates' => WebhookCheckService::DEFAULT_TEMPLATES, ]); } public function store(Request $request) { $data = $request->validate([ 'name' => 'required|string|max:255', 'url' => 'required|url|max:500', 'secret' => 'nullable|string|max:255', 'is_active' => 'boolean', 'events' => 'required|array|min:1', 'events.*' => 'string|in:' . implode(',', array_keys(WebhookCheckService::EVENTS)), 'thresholds' => 'nullable|array', 'device_filter' => 'nullable|array', 'tracked_clients' => 'nullable|array', 'templates' => 'nullable|array', 'cooldown_minutes' => 'integer|min:1|max:1440', ]); WebhookConfig::create($data); return back()->with('success', 'Webhook created.'); } public function update(Request $request, WebhookConfig $webhook) { $data = $request->validate([ 'name' => 'required|string|max:255', 'url' => 'required|url|max:500', 'secret' => 'nullable|string|max:255', 'is_active' => 'boolean', 'events' => 'required|array|min:1', 'events.*' => 'string|in:' . implode(',', array_keys(WebhookCheckService::EVENTS)), 'thresholds' => 'nullable|array', 'device_filter' => 'nullable|array', 'tracked_clients' => 'nullable|array', 'templates' => 'nullable|array', 'cooldown_minutes' => 'integer|min:1|max:1440', ]); $webhook->update($data); return back()->with('success', 'Webhook updated.'); } public function destroy(WebhookConfig $webhook) { $webhook->delete(); return back()->with('success', 'Webhook deleted.'); } public function test(WebhookConfig $webhook) { return $this->fireTest($webhook->url, $webhook->secret); } /** * Test an arbitrary URL+secret before the webhook is saved. Lets the * operator validate their endpoint from the form without first * committing a row. */ public function testUrl(Request $request) { $data = $request->validate([ 'url' => 'required|url|max:500', 'secret' => 'nullable|string|max:255', ]); return $this->fireTest($data['url'], $data['secret'] ?? null); } private function fireTest(string $url, ?string $secret) { $message = '✅ Test webhook from ' . config('app.name') . ' — endpoint is reachable.'; $genericPayload = [ 'event' => 'test', 'timestamp' => now()->toIso8601String(), 'message' => $message, 'data' => ['message' => $message], ]; // Shape the payload to match the target platform (Google Chat, // Slack, Discord, Teams) so the test exercises the same code // path real events use. $payload = \Dashboard\Unifi\Services\WebhookCheckService::buildPlatformPayload($url, $message, $genericPayload); $headers = ['Content-Type' => 'application/json']; if ($secret) { $headers['X-Webhook-Signature'] = hash_hmac('sha256', json_encode($payload), $secret); } try { $response = \Illuminate\Support\Facades\Http::withHeaders($headers)->timeout(10)->post($url, $payload); return response()->json([ 'ok' => $response->successful(), 'status' => $response->status(), 'body' => mb_substr((string) $response->body(), 0, 500), ]); } catch (\Throwable $e) { return response()->json(['ok' => false, 'error' => $e->getMessage()], 422); } } }