docs/plans/2025-12-30-webhook-events-redesign.md
Date: 2025-12-30 Status: Design Author: Danny
This document outlines a redesign of the webhook notification system to reduce noise, improve signal quality, and provide better targeting for notifications. The current system sends too many notifications for granular events (field updates, task status changes) while sometimes missing important outcomes (pipeline failures, completions).
Approval Events (Direct Message capable):
ISSUE_APPROVAL_REQUESTED - Replaces ISSUE_APPROVAL_NOTIFY
ISSUE_SENT_BACK - New event type
Issue Lifecycle Events (Channel only):
ISSUE_CREATED - Replaces ISSUE_CREATE
Pipeline Events (Channel only):
PIPELINE_FAILED - New event type with aggregation
PIPELINE_COMPLETED - New event type
These existing events will be deprecated:
ISSUE_COMMENT_CREATEISSUE_FIELD_UPDATEISSUE_STATUS_UPDATENOTIFY_ISSUE_APPROVEDNOTIFY_PIPELINE_ROLLOUTISSUE_PIPELINE_STAGE_STATUS_UPDATEPIPELINE_COMPLETEDISSUE_PIPELINE_TASK_RUN_STATUS_UPDATEPIPELINE_FAILED| Old Event Type | New Event Type | Notes |
|---|---|---|
ISSUE_CREATE (1) | ISSUE_CREATED (10) | Renamed for consistency |
ISSUE_APPROVAL_NOTIFY (6) | ISSUE_APPROVAL_REQUESTED (11) | Covers initial + re-request |
ISSUE_PIPELINE_TASK_RUN_STATUS_UPDATE (7) | PIPELINE_FAILED (13) | Aggregated failures |
ISSUE_PIPELINE_STAGE_STATUS_UPDATE (5) | PIPELINE_COMPLETED (14) | Success only |
The component layer provides raw event data using proto-generated enums:
type WebhookContext struct {
EventType storepb.Activity_Type // Proto enum: ISSUE_CREATED, etc.
// Direct messaging configuration
DirectMessage bool
// Event-specific data (only one populated per event)
IssueCreated *IssueCreatedEvent
ApprovalRequested *IssueApprovalRequestedEvent
SentBack *IssueSentBackEvent
PipelineFailed *PipelineFailedEvent
PipelineCompleted *PipelineCompletedEvent
}
type IssueCreatedEvent struct {
IssueID int64
IssueTitle string
IssueType string // "DATABASE_CHANGE", "GRANT_REQUEST", etc.
ProjectID string
CreatorName string
CreatorEmail string
}
type IssueApprovalRequestedEvent struct {
IssueID int64
IssueTitle string
ProjectID string
ApprovalRole string // "Project Owner"
RequiredCount int // How many approvals needed
Approvers []User // List of people to notify
}
type IssueSentBackEvent struct {
IssueID int64
IssueTitle string
ProjectID string
ApproverName string
ApproverEmail string
Reason string // Rejection comment
CreatorName string
CreatorEmail string // Person to notify
}
type PipelineFailedEvent struct {
IssueID int64
IssueTitle string
PlanID int64
FailedTasks []FailedTask
}
type FailedTask struct {
TaskID int64
TaskName string
DatabaseName string
InstanceName string
ErrorMessage string
FailedAt time.Time
}
type PipelineCompletedEvent struct {
IssueID int64
IssueTitle string
PlanID int64
TotalTasks int
StartedAt time.Time
CompletedAt time.Time
}
type User struct {
Name string
Email string
}
Each plugin (Slack, Discord, Teams, Feishu, etc.) receives the WebhookContext and handles:
Plugins decide all presentation details. The component layer only provides raw data.
Uses existing project_webhook table - no schema changes needed:
CREATE TABLE project_webhook (
id serial PRIMARY KEY,
project text NOT NULL REFERENCES project(resource_id),
-- Stored as ProjectWebhook proto
payload jsonb NOT NULL DEFAULT '{}'
);
Update proto/store/store/project_webhook.proto - modify Activity.Type enum:
Add new activity types:
enum Activity {
enum Type {
// ... existing types ...
// New focused event types
ISSUE_CREATED = 10;
ISSUE_APPROVAL_REQUESTED = 11;
ISSUE_SENT_BACK = 12;
PIPELINE_FAILED = 13;
PIPELINE_COMPLETED = 14;
}
}
Existing ProjectWebhook proto (no changes):
message ProjectWebhook {
WebhookType type = 1; // SLACK, DISCORD, TEAMS, etc.
string title = 2;
string url = 3;
repeated Activity.Type activities = 4; // Event subscriptions
bool direct_message = 5; // Enable IM direct messaging
}
The direct_message field in ProjectWebhook controls delivery:
When direct_message = true:
ISSUE_APPROVAL_REQUESTED → sends individual DMs to each user in Approvers[]ISSUE_SENT_BACK → sends DM to CreatorEmailurl (channel events ignore DM flag)When direct_message = false:
urlIM Integration: Direct messaging requires IM integration settings in the webhook payload JSONB:
{
"im_type": "feishu",
"app_id": "cli_xxx",
"app_secret": "xxx"
}
Multiple tasks failing in quick succession creates notification spam. For example, a migration failing across 10 databases could trigger 10 separate notifications.
5-minute debounced aggregation window:
PIPELINE_FAILED event with all failed tasksApproach: In-memory state tracking (non-HA)
Pipeline events will use in-memory aggregation similar to the existing RunningTaskRuns pattern:
type PipelineFailureWindow struct {
FirstFailureTime time.Time
FailedTasks []*FailedTaskInfo
NotificationSent bool
Timer *time.Timer
}
Location: Extend existing ListenTaskSkippedOrDone in backend/runner/taskrun/scheduler.go
Future Migration:
webhook_pending_event table for HA when neededFOR UPDATE SKIP LOCKED pattern for distributed coordination| Event | Trigger Location | Condition |
|---|---|---|
ISSUE_CREATED | backend/api/v1/issue_service.go - CreateIssue() | After issue persisted to DB |
ISSUE_APPROVAL_REQUESTED | Approval runner or issue update handler | Approval flow starts or re-requested |
ISSUE_SENT_BACK | Approval update handler | Approver rejects/sends back |
PIPELINE_FAILED | backend/runner/taskrun/scheduler.go | 5-min after first failure |
PIPELINE_COMPLETED | Extend ListenTaskSkippedOrDone in scheduler.go | All tasks terminal, no failures |
WebhookContext with appropriate event dataproject_webhook table for webhooks subscribed to this event typedirect_message = true AND event is approval-related → send individual DMsISSUE_APPROVAL_REQUESTED:
END_USER principal type only (no service accounts)ISSUE_SENT_BACK:
Other events:
proto/store/store/project_webhook.proto - add new Activity.Type values (10-14)cd proto && buf format -w and buf generatebackend/component/webhook/WebhookContext struct to use proto enumsISSUE_CREATED trigger in backend/api/v1/issue_service.goISSUE_APPROVAL_REQUESTED trigger in approval handlersISSUE_SENT_BACK trigger in approval update handlerbackend/runner/taskrun/scheduler.gobackend/plugin/webhook/slack/backend/plugin/webhook/discord.gobackend/plugin/webhook/teams.gobackend/plugin/webhook/feishu.goOld Activity.Type values (1-9) will remain in the proto but marked as deprecated. Existing webhooks subscribed to old events will continue working until users update their subscriptions.
Migration path:
Current implementation uses in-memory state for failure aggregation. For multi-replica HA deployments:
Proposed approach:
webhook_pending_event tableFOR UPDATE SKIP LOCKED for distributed coordinationPotential future additions based on user feedback:
ISSUE_CANCELED - When issue is explicitly canceledISSUE_COMPLETED - When issue reaches done statePIPELINE_CANCELED - When pipeline is stopped by userThese are intentionally excluded from initial design to keep it focused.
This redesign reduces webhook noise while improving signal quality:
| Metric | Before | After |
|---|---|---|
| Event types | 9 | 5 |
| Noisy events | Yes (comments, field updates) | No |
| Pipeline spam | Yes (per-task notifications) | No (5-min aggregation) |
| Action targeting | Partial (DM for approvals) | Better (DM for all approval events) |
Key benefits: