Merge pull request #1961 from antonio-mello-ai/fix/webhook-github-sentry-signing-modes

feat(server): add github_hmac and none webhook signing modes
This commit is contained in:
Dotta 2026-04-07 22:58:14 -05:00 committed by GitHub
commit 9cfa37fce3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 114 additions and 15 deletions

View file

@ -61,7 +61,7 @@ import type { RoutineTrigger, RoutineVariable } from "@paperclipai/shared";
const concurrencyPolicies = ["coalesce_if_active", "always_enqueue", "skip_if_active"];
const catchUpPolicies = ["skip_missed", "enqueue_missed_with_cap"];
const triggerKinds = ["schedule", "webhook"];
const signingModes = ["bearer", "hmac_sha256"];
const signingModes = ["bearer", "hmac_sha256", "github_hmac", "none"];
const routineTabs = ["triggers", "runs", "activity"] as const;
const concurrencyPolicyDescriptions: Record<string, string> = {
coalesce_if_active: "Keep one follow-up run queued while an active run is still working.",
@ -75,7 +75,10 @@ const catchUpPolicyDescriptions: Record<string, string> = {
const signingModeDescriptions: Record<string, string> = {
bearer: "Expect a shared bearer token in the Authorization header.",
hmac_sha256: "Expect an HMAC SHA-256 signature over the request using the shared secret.",
github_hmac: "Accept GitHub-style X-Hub-Signature-256 header (HMAC over raw body, no timestamp).",
none: "No authentication — the webhook URL itself acts as a shared secret.",
};
const SIGNING_MODES_WITHOUT_REPLAY_WINDOW = new Set(["github_hmac", "none"]);
type RoutineTab = (typeof routineTabs)[number];
@ -198,13 +201,15 @@ function TriggerEditor({
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-xs">Replay window (seconds)</Label>
<Input
value={draft.replayWindowSec}
onChange={(event) => setDraft((current) => ({ ...current, replayWindowSec: event.target.value }))}
/>
</div>
{!SIGNING_MODES_WITHOUT_REPLAY_WINDOW.has(draft.signingMode) && (
<div className="space-y-1.5">
<Label className="text-xs">Replay window (seconds)</Label>
<Input
value={draft.replayWindowSec}
onChange={(event) => setDraft((current) => ({ ...current, replayWindowSec: event.target.value }))}
/>
</div>
)}
</>
)}
</div>
@ -987,10 +992,12 @@ export function RoutineDetail() {
</Select>
<p className="text-xs text-muted-foreground">{signingModeDescriptions[newTrigger.signingMode]}</p>
</div>
<div className="space-y-1.5">
<Label className="text-xs">Replay window (seconds)</Label>
<Input value={newTrigger.replayWindowSec} onChange={(event) => setNewTrigger((current) => ({ ...current, replayWindowSec: event.target.value }))} />
</div>
{!SIGNING_MODES_WITHOUT_REPLAY_WINDOW.has(newTrigger.signingMode) && (
<div className="space-y-1.5">
<Label className="text-xs">Replay window (seconds)</Label>
<Input value={newTrigger.replayWindowSec} onChange={(event) => setNewTrigger((current) => ({ ...current, replayWindowSec: event.target.value }))} />
</div>
)}
</>
)}
</div>