false, 'error' => 'POST required'], 405); } $secret = trim((string) cfg('build.webhook_secret', '')); $raw = file_get_contents('php://input') ?: ''; $sig = (string) ($_SERVER['HTTP_X_GITEA_SIGNATURE'] ?? $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? ''); if ($secret !== '') { $expected = hash_hmac('sha256', $raw, $secret); if ($sig === '' || !hash_equals($expected, $sig)) { json_out(['ok' => false, 'error' => 'bad_signature'], 403); } } $payload = json_decode($raw, true); if (!is_array($payload)) { json_out(['ok' => false, 'error' => 'invalid_json'], 400); } try { $build = BuildRunner::enqueueFromWebhook($payload); json_out(['ok' => true, 'build' => $build]); } catch (InvalidArgumentException $e) { json_out(['ok' => false, 'error' => $e->getMessage()], 422); } catch (Throwable $e) { error_log('build_webhook: ' . $e->getMessage()); json_out(['ok' => false, 'error' => 'enqueue_failed'], 500); }