diff --git a/orchestrator/src/client/pages/SettingsPage.tsx b/orchestrator/src/client/pages/SettingsPage.tsx
index cae31d2..075286a 100644
--- a/orchestrator/src/client/pages/SettingsPage.tsx
+++ b/orchestrator/src/client/pages/SettingsPage.tsx
@@ -16,10 +16,9 @@ import { DangerZoneSection } from "@client/pages/settings/components/DangerZoneS
import { DisplaySettingsSection } from "@client/pages/settings/components/DisplaySettingsSection"
import { EnvironmentSettingsSection } from "@client/pages/settings/components/EnvironmentSettingsSection"
import { GradcrackerSection } from "@client/pages/settings/components/GradcrackerSection"
-import { JobCompleteWebhookSection } from "@client/pages/settings/components/JobCompleteWebhookSection"
import { JobspySection } from "@client/pages/settings/components/JobspySection"
import { ModelSettingsSection } from "@client/pages/settings/components/ModelSettingsSection"
-import { PipelineWebhookSection } from "@client/pages/settings/components/PipelineWebhookSection"
+import { WebhooksSection } from "@client/pages/settings/components/WebhooksSection"
import { ResumeProjectsSection } from "@client/pages/settings/components/ResumeProjectsSection"
import { SearchTermsSection } from "@client/pages/settings/components/SearchTermsSection"
import { UkvisajobsSection } from "@client/pages/settings/components/UkvisajobsSection"
@@ -452,13 +451,10 @@ export const SettingsPage: React.FC = () => {
isLoading={isLoading}
isSaving={isSaving}
/>
-
-
diff --git a/orchestrator/src/client/pages/settings/components/EnvironmentSettingsSection.test.tsx b/orchestrator/src/client/pages/settings/components/EnvironmentSettingsSection.test.tsx
index ef117d6..8801d2e 100644
--- a/orchestrator/src/client/pages/settings/components/EnvironmentSettingsSection.test.tsx
+++ b/orchestrator/src/client/pages/settings/components/EnvironmentSettingsSection.test.tsx
@@ -56,7 +56,6 @@ describe("EnvironmentSettingsSection", () => {
expect(screen.getByText("sk-1********")).toBeInTheDocument()
expect(screen.getByText("pass********")).toBeInTheDocument()
expect(screen.getByText("abcd********")).toBeInTheDocument()
- expect(screen.getByText("sec-********")).toBeInTheDocument()
expect(screen.getByText("Not set")).toBeInTheDocument()
// Basic Auth
diff --git a/orchestrator/src/client/pages/settings/components/EnvironmentSettingsSection.tsx b/orchestrator/src/client/pages/settings/components/EnvironmentSettingsSection.tsx
index 583c3a8..c7511d5 100644
--- a/orchestrator/src/client/pages/settings/components/EnvironmentSettingsSection.tsx
+++ b/orchestrator/src/client/pages/settings/components/EnvironmentSettingsSection.tsx
@@ -50,16 +50,6 @@ export const EnvironmentSettingsSection: React.FC
-
-
diff --git a/orchestrator/src/client/pages/settings/components/JobCompleteWebhookSection.tsx b/orchestrator/src/client/pages/settings/components/JobCompleteWebhookSection.tsx
deleted file mode 100644
index fa0c2c3..0000000
--- a/orchestrator/src/client/pages/settings/components/JobCompleteWebhookSection.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React from "react"
-import { useFormContext } from "react-hook-form"
-
-import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
-import { UpdateSettingsInput } from "@shared/settings-schema"
-import type { WebhookValues } from "@client/pages/settings/types"
-import { SettingsInput } from "@client/pages/settings/components/SettingsInput"
-
-type JobCompleteWebhookSectionProps = {
- values: WebhookValues
- isLoading: boolean
- isSaving: boolean
-}
-
-export const JobCompleteWebhookSection: React.FC = ({
- values,
- isLoading,
- isSaving,
-}) => {
- const { default: defaultJobCompleteWebhookUrl, effective: effectiveJobCompleteWebhookUrl } = values
- const { register, formState: { errors } } = useFormContext()
-
- return (
-
-
- Job Complete Webhook
-
-
-
-
-
-
-
- )
-}
diff --git a/orchestrator/src/client/pages/settings/components/PipelineWebhookSection.tsx b/orchestrator/src/client/pages/settings/components/PipelineWebhookSection.tsx
deleted file mode 100644
index 7339866..0000000
--- a/orchestrator/src/client/pages/settings/components/PipelineWebhookSection.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React from "react"
-import { useFormContext } from "react-hook-form"
-
-import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
-import { UpdateSettingsInput } from "@shared/settings-schema"
-import type { WebhookValues } from "@client/pages/settings/types"
-import { SettingsInput } from "@client/pages/settings/components/SettingsInput"
-
-type PipelineWebhookSectionProps = {
- values: WebhookValues
- isLoading: boolean
- isSaving: boolean
-}
-
-export const PipelineWebhookSection: React.FC = ({
- values,
- isLoading,
- isSaving,
-}) => {
- const { default: defaultPipelineWebhookUrl, effective: effectivePipelineWebhookUrl } = values
- const { register, formState: { errors } } = useFormContext()
-
- return (
-
-
- Pipeline Webhook
-
-
-
-
-
-
-
- )
-}
diff --git a/orchestrator/src/client/pages/settings/components/WebhooksSection.test.tsx b/orchestrator/src/client/pages/settings/components/WebhooksSection.test.tsx
new file mode 100644
index 0000000..1547fa2
--- /dev/null
+++ b/orchestrator/src/client/pages/settings/components/WebhooksSection.test.tsx
@@ -0,0 +1,50 @@
+import { render, screen } from "@testing-library/react"
+import { useForm, FormProvider } from "react-hook-form"
+
+import { Accordion } from "@/components/ui/accordion"
+import { WebhooksSection } from "./WebhooksSection"
+import { UpdateSettingsInput } from "@shared/settings-schema"
+
+const WebhooksHarness = () => {
+ const methods = useForm({
+ defaultValues: {
+ pipelineWebhookUrl: "https://pipeline.com",
+ jobCompleteWebhookUrl: "https://job.com",
+ webhookSecret: "",
+ }
+ })
+
+ return (
+
+
+
+
+
+ )
+}
+
+describe("WebhooksSection", () => {
+ it("renders both webhook sections and the secret", () => {
+ render()
+
+ expect(screen.getByText("Pipeline Status")).toBeInTheDocument()
+ expect(screen.getByText("Job Completion")).toBeInTheDocument()
+
+ expect(screen.getByDisplayValue("https://pipeline.com")).toBeInTheDocument()
+ expect(screen.getByDisplayValue("https://job.com")).toBeInTheDocument()
+
+ expect(screen.getByText("sec-********")).toBeInTheDocument()
+ })
+})
diff --git a/orchestrator/src/client/pages/settings/components/WebhooksSection.tsx b/orchestrator/src/client/pages/settings/components/WebhooksSection.tsx
new file mode 100644
index 0000000..5c21850
--- /dev/null
+++ b/orchestrator/src/client/pages/settings/components/WebhooksSection.tsx
@@ -0,0 +1,80 @@
+import React from "react"
+import { useFormContext } from "react-hook-form"
+
+import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
+import { Separator } from "@/components/ui/separator"
+import { UpdateSettingsInput } from "@shared/settings-schema"
+import type { WebhookValues } from "@client/pages/settings/types"
+import { SettingsInput } from "@client/pages/settings/components/SettingsInput"
+
+type WebhooksSectionProps = {
+ pipelineWebhook: WebhookValues
+ jobCompleteWebhook: WebhookValues
+ webhookSecretHint: string | null
+ isLoading: boolean
+ isSaving: boolean
+}
+
+export const WebhooksSection: React.FC = ({
+ pipelineWebhook,
+ jobCompleteWebhook,
+ webhookSecretHint,
+ isLoading,
+ isSaving,
+}) => {
+ const { register, formState: { errors } } = useFormContext()
+
+ const formatSecretHint = (hint: string | null) => (hint ? `${hint}********` : "Not set")
+
+ return (
+
+
+ Webhooks
+
+
+
+
+
+
+
+
+
Job Completion
+
+
+
+
+
+
+
+
+
+ )
+}