Skip to content

Commit 9884d50

Browse files
author
Test User
committed
feat: expose webhook trigger settings
1 parent 4d2e8ba commit 9884d50

6 files changed

Lines changed: 455 additions & 12 deletions

File tree

docs/integrations/integrations.md

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,30 @@ Night Watch CLI integrates with several third-party services to enable notificat
66

77
## Inbound Webhook Triggers
88

9+
Configure inbound triggers from Settings -> Integrations -> Inbound Webhook
10+
Triggers. The same values are stored in the `webhookTriggers` config block for
11+
users who manage configuration as code.
12+
13+
The endpoint format is `/api/jobs/:id/run` in single-project mode and
14+
`/api/projects/:projectId/jobs/:id/run` in global mode. For GitHub webhooks,
15+
the matched rule's `jobId` is dispatched. If the signature is valid but no rule
16+
matches, Night Watch returns `202` with `accepted: false` and does not start a
17+
job.
18+
19+
### Signed curl
20+
21+
Use the Night Watch signature header for generic webhook clients:
22+
23+
```bash
24+
payload='{"source":"manual"}'
25+
signature=$(printf '%s' "$payload" | openssl dgst -sha256 -hmac "$NIGHT_WATCH_WEBHOOK_SECRET" | awk '{print $2}')
26+
27+
curl -X POST 'https://YOUR_NIGHT_WATCH_HOST/api/jobs/reviewer/run' \
28+
-H 'Content-Type: application/json' \
29+
-H "X-Night-Watch-Signature: sha256=$signature" \
30+
--data "$payload"
31+
```
32+
933
### GitHub Webhooks
1034

1135
GitHub repository webhooks can start a Night Watch job when a configured event rule matches.
@@ -19,14 +43,20 @@ Night Watch verifies GitHub's `X-Hub-Signature-256` HMAC signature before it eva
1943
export NIGHT_WATCH_WEBHOOK_SECRET="your-random-webhook-secret"
2044
```
2145

22-
2. In GitHub, go to Repository Settings -> Webhooks -> Add webhook:
46+
2. In Settings -> Integrations, enable Inbound Webhook Triggers:
47+
- Secret Environment Variable: `NIGHT_WATCH_WEBHOOK_SECRET`
48+
- Allowed Jobs: choose each job that external callers may dispatch
49+
- GitHub Events: enable GitHub and select the events used by your rules
50+
- GitHub Rules: map event/action/branch/failure filters to a Night Watch job
51+
52+
3. In GitHub, go to Repository Settings -> Webhooks -> Add webhook:
2353
- Payload URL: `https://YOUR_NIGHT_WATCH_HOST/api/jobs/reviewer/run`
2454
- Content type: `application/json`
2555
- Secret: the same value as `NIGHT_WATCH_WEBHOOK_SECRET`
2656
- SSL verification: enabled
2757
- Selected events: choose the events used by your rules, such as `workflow_run`, `check_suite`, `pull_request`, or `repository_dispatch`
2858

29-
3. Enable webhook triggers in `night-watch.config.json`:
59+
4. Optional config-file equivalent:
3060

3161
```json
3262
{
@@ -51,12 +81,6 @@ export NIGHT_WATCH_WEBHOOK_SECRET="your-random-webhook-secret"
5181
}
5282
```
5383

54-
The endpoint format is `/api/jobs/:id/run` in single-project mode and
55-
`/api/projects/:projectId/jobs/:id/run` in global mode. For GitHub webhooks,
56-
the matched rule's `jobId` is dispatched. If the signature is valid but no rule
57-
matches, Night Watch returns `202` with `accepted: false` and does not start a
58-
job.
59-
6084
## Notification Integrations
6185

6286
### Slack

packages/core/src/shared/types.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,31 @@ export interface INotificationConfig {
128128
webhooks: IWebhookConfig[];
129129
}
130130

131+
// ==================== Inbound Webhook Triggers ====================
132+
133+
export interface IWebhookTriggerGithubRule {
134+
event: string;
135+
action?: string;
136+
jobId: JobType;
137+
branchPatterns?: string[];
138+
onlyOnFailure?: boolean;
139+
}
140+
141+
export interface IWebhookTriggerGithubConfig {
142+
enabled: boolean;
143+
events: string[];
144+
rules: IWebhookTriggerGithubRule[];
145+
}
146+
147+
export interface IWebhookTriggerConfig {
148+
enabled: boolean;
149+
secretEnv: string;
150+
allowedJobIds: JobType[];
151+
requireTimestamp: boolean;
152+
maxSkewSeconds: number;
153+
github: IWebhookTriggerGithubConfig;
154+
}
155+
131156
// ==================== Roadmap Scanner Config ====================
132157

133158
export interface IRoadmapScannerConfig {
@@ -281,6 +306,8 @@ export interface INightWatchConfig {
281306
prResolver?: IPrResolverConfig;
282307
merger?: IMergerConfig;
283308
queue: IQueueConfig;
309+
/** Authenticated inbound job webhook trigger configuration */
310+
webhookTriggers?: IWebhookTriggerConfig;
284311
/** Time-based provider schedule overrides */
285312
providerScheduleOverrides?: IProviderScheduleOverride[];
286313
}

web/api.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import type {
2929
IRoadmapStatus,
3030
IStatusSnapshot,
3131
IWebhookConfig,
32+
IWebhookTriggerConfig,
33+
IWebhookTriggerGithubConfig,
34+
IWebhookTriggerGithubRule,
3235
JobType,
3336
MergeMethod,
3437
QaArtifacts,
@@ -41,7 +44,8 @@ import { getWebJobDef } from './utils/jobs';
4144
export type {
4245
ClaudeModel, DayOfWeek, IAnalyticsConfig, IAuditConfig, IBoardProviderConfig, IJobProviders, ILogInfo, IMergerConfig, INightWatchConfig,
4346
INotificationConfig, IPrdInfo, IProviderBucketConfig, IProviderPreset, IProviderScheduleOverride, IPrInfo, IProcessInfo, IQaConfig,
44-
IPrResolverConfig, IQueueConfig, IRoadmapItem, IRoadmapScannerConfig, IRoadmapStatus, IStatusSnapshot, IWebhookConfig,
47+
IPrResolverConfig, IQueueConfig, IRoadmapItem, IRoadmapScannerConfig, IRoadmapStatus, IStatusSnapshot, IWebhookConfig, IWebhookTriggerConfig,
48+
IWebhookTriggerGithubConfig, IWebhookTriggerGithubRule,
4549
JobType, MergeMethod, QaArtifacts, QueueMode
4650
};
4751

web/pages/Settings.tsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ import {
1919
IQaConfig,
2020
IRoadmapScannerConfig,
2121
IWebhookConfig,
22+
IWebhookTriggerConfig,
23+
JobType,
2224
removeProject,
2325
triggerInstallCron,
2426
updateConfig,
2527
updateGlobalNotifications,
2628
useApi,
2729
} from '../api';
28-
import WebhookEditor from '../components/settings/WebhookEditor.js';
2930
import PresetFormModal from '../components/providers/PresetFormModal.js';
3031
import Button from '../components/ui/Button';
31-
import Card from '../components/ui/Card';
3232
import Badge from '../components/ui/Badge.js';
3333
import Tabs from '../components/ui/Tabs';
3434
import { useStore } from '../store/useStore';
@@ -59,6 +59,30 @@ const JOB_PROVIDER_KEYS: Array<keyof IJobProviders> = [
5959
'merger',
6060
];
6161

62+
const WEBHOOK_TRIGGER_JOB_IDS: JobType[] = [
63+
'executor',
64+
'reviewer',
65+
'qa',
66+
'audit',
67+
'slicer',
68+
'analytics',
69+
'pr-resolver',
70+
'merger',
71+
];
72+
73+
const DEFAULT_WEBHOOK_TRIGGERS: IWebhookTriggerConfig = {
74+
enabled: false,
75+
secretEnv: 'NIGHT_WATCH_WEBHOOK_SECRET',
76+
allowedJobIds: WEBHOOK_TRIGGER_JOB_IDS,
77+
requireTimestamp: false,
78+
maxSkewSeconds: 300,
79+
github: {
80+
enabled: false,
81+
events: [],
82+
rules: [],
83+
},
84+
};
85+
6286
type ConfigForm = {
6387
provider: INightWatchConfig['provider'];
6488
providerLabel: string;
@@ -103,6 +127,7 @@ type ConfigForm = {
103127
prResolver: IPrResolverConfig;
104128
merger: IMergerConfig;
105129
queue: INightWatchConfig['queue'];
130+
webhookTriggers: IWebhookTriggerConfig;
106131
};
107132

108133
const toFormState = (config: INightWatchConfig): ConfigForm => {
@@ -174,6 +199,7 @@ const toFormState = (config: INightWatchConfig): ConfigForm => {
174199
},
175200
providerBuckets: {},
176201
},
202+
webhookTriggers: config.webhookTriggers ?? DEFAULT_WEBHOOK_TRIGGERS,
177203
};
178204
};
179205

@@ -377,6 +403,7 @@ const Settings: React.FC = () => {
377403
prResolver: form.prResolver,
378404
merger: form.merger,
379405
queue: form.queue,
406+
webhookTriggers: form.webhookTriggers,
380407
});
381408

382409
// Update form directly from server response to ensure it reflects persisted values
@@ -555,8 +582,10 @@ const Settings: React.FC = () => {
555582
form={form}
556583
updateField={updateField as <K extends keyof typeof form>(key: K, value: (typeof form)[K]) => void}
557584
globalWebhook={globalWebhook}
585+
isGlobalMode={isGlobalMode}
558586
onSetGlobal={handleSetGlobal}
559587
onUnsetGlobal={handleUnsetGlobal}
588+
selectedProjectId={selectedProjectId}
560589
/>
561590
),
562591
},

0 commit comments

Comments
 (0)