authorised($request)) { Log::warning('Mantis webhook authentication failed', [ 'event' => $request->input('event'), 'issue_id' => $request->input('issue.id'), ]); return response()->json([ 'message' => 'Unauthorised', ], 401); } /** @var array{event: string, issue: array{id: int|string, summary?: string, severity?: string, priority?: string}} $payload */ $payload = Validator::make($request->all(), [ 'event' => ['required', 'string'], 'issue' => ['required', 'array'], 'issue.id' => ['required', 'integer'], 'issue.summary' => ['sometimes', 'string'], 'issue.severity' => ['sometimes', 'string'], 'issue.priority' => ['sometimes', 'string'], ])->validate(); $issueId = (int) $payload['issue']['id']; if ($payload['event'] !== 'issue.opened') { Log::info('Mantis webhook ignored event', [ 'event' => $payload['event'], 'issue_id' => $issueId, ]); return response()->noContent(); } $profile = $profileSelector->pickFor($payload['issue']); if (! $profile instanceof AgentProfile) { Log::info('Mantis webhook found no matching profile for issue', [ 'issue_id' => $issueId, ]); return response()->json([ 'status' => 'accepted', 'dispatched' => false, ]); } DispatchMantisTicketJob::dispatch($issueId); Log::info('Mantis webhook dispatched issue', [ 'issue_id' => $issueId, 'profile_id' => $profile->getKey(), ]); return response()->json([ 'status' => 'accepted', 'dispatched' => true, ]); } private function authorised(Request $request): bool { $expectedSecret = (string) config('agentic.mantis.webhook_secret'); $providedSecret = (string) $request->header('X-Mantis-Webhook-Secret'); if ($expectedSecret === '' || $providedSecret === '') { return false; } return hash_equals($expectedSecret, $providedSecret); } }