Documentation
¶
Overview ¶
Package webhook fires a single HTTP POST when a scan reaches a terminal state (completed, failed or cancelled). Unlike the per-finding notify backends, this is a one-shot scan-completion hook: project-scoped, retried up to 5 times on 5xx/network errors, and asynchronous so it never blocks the scan. A short-lived process (the CLI) calls Drain before it exits so the POST is not lost with the process; a long-lived one (the server) never needs to.
Index ¶
- Constants
- func AgenticEvent(status string) (event, completeness, stopReason string)
- func Drain(timeout time.Duration) (undelivered int)
- func FireAgenticScan(settings *config.Settings, repo *database.Repository, agenticScanUUID string)
- func FireNativeScan(settings *config.Settings, repo *database.Repository, scanUUID string)
- func FireNativeScanOutcome(settings *config.Settings, repo *database.Repository, scanUUID string, ...)
- func NativeEvent(status, errMsg, stopReason string) (event, outStatus string, terminal bool)
- type FindingCounts
- type Outcome
- type ScanCompletedPayload
- type Sender
Constants ¶
const ( EventScanCompleted = "scan.completed" EventScanFailed = "scan.failed" EventScanCancelled = "scan.cancelled" )
Webhook event values. scan.completed is the one receivers have always seen; the other two are additive, so a receiver filtering on the old value gets exactly what it got before.
Variables ¶
This section is empty.
Functions ¶
func AgenticEvent ¶ added in v0.5.3
AgenticEvent maps an agentic run's status to the webhook event. The status itself is reported verbatim. A run that stopped at its time or budget limit ran, so it is scan.completed with completeness "partial" and the limit as stop_reason — the same shape as a curtailed native scan. A status outside the terminal set is scan.completed, as the hook always reported it.
func Drain ¶ added in v0.5.3
Drain waits up to timeout for every PostAsync delivery started so far to finish (delivered, or failed after its retries) and returns how many were still in flight when it gave up. A process that is about to exit calls it so its webhooks are not killed mid-POST; 0 means nothing was left behind.
func FireAgenticScan ¶
func FireAgenticScan(settings *config.Settings, repo *database.Repository, agenticScanUUID string)
FireAgenticScan loads the agentic scan row, computes severity counts from associated findings, and posts a completion payload. No-op when settings/repo are nil or the webhook is not enabled.
func FireNativeScan ¶
func FireNativeScan(settings *config.Settings, repo *database.Repository, scanUUID string)
FireNativeScan loads the native scan row and posts its terminal payload. No-op when settings are nil, the webhook is not enabled, or the outcome is filtered out by notify.webhook.events.
func FireNativeScanOutcome ¶ added in v0.5.3
func FireNativeScanOutcome(settings *config.Settings, repo *database.Repository, scanUUID string, out Outcome)
FireNativeScanOutcome is FireNativeScan with the caller's view of the run, used when the row may be missing or unfinished (see Outcome). The payload is built synchronously — the row is read before this returns, so the caller may close or discard the database right after — and posted asynchronously.
func NativeEvent ¶ added in v0.5.3
NativeEvent maps a native scan row's status to the webhook event and the payload status. terminal is false for a status that is not an end state (running, pending), which is reported as scan.completed with the status verbatim — what the hook sent before it knew other events.
A cancelled scan is stored as status "failed" with error_message "cancelled" (stop_reason cancelled); it is reported as scan.cancelled with status "cancelled". A curtailed scan is "completed" with completeness "partial" and stays scan.completed.
Types ¶
type FindingCounts ¶
type FindingCounts struct {
Total int `json:"total"`
BySeverity map[string]int `json:"by_severity"`
}
FindingCounts is the severity breakdown of findings produced by a scan.
type Outcome ¶ added in v0.5.3
type Outcome struct {
// Err is the run's own failure (not an export failure); nil on success.
Err error
// Interrupted reports that a signal stopped the run.
Interrupted bool
ProjectUUID string
Target string
StartedAt time.Time
}
Outcome is what the firing caller knows about a run beyond its scan row. The row is authoritative whenever it holds a terminal status; Outcome decides only when the row is missing (the run failed before creating it) or never reached a terminal status. The fields after Interrupted fill the payload in that case.
type ScanCompletedPayload ¶
type ScanCompletedPayload struct {
// Event is scan.completed, scan.failed or scan.cancelled (see EventFor*).
Event string `json:"event"`
ProjectUUID string `json:"project_uuid"`
ScanUUID string `json:"scan_uuid"`
ScanType string `json:"scan_type"`
Target string `json:"target"`
Status string `json:"status"`
// Completeness is "complete" or "partial" when the run knows (a curtailed
// scan is still scan.completed, with completeness "partial"); StopReason
// names why it stopped short. Both empty when unknown.
Completeness string `json:"completeness,omitempty"`
StopReason string `json:"stop_reason,omitempty"`
// Error is the failure message on scan.failed.
Error string `json:"error,omitempty"`
StartedAt time.Time `json:"started_at"`
FinishedAt time.Time `json:"finished_at"`
Findings FindingCounts `json:"findings"`
ResultURL string `json:"result_url"`
}
ScanCompletedPayload is the JSON body POSTed to the webhook URL. The name predates the failed/cancelled events; every terminal outcome uses it.
func NativeScanPayload ¶ added in v0.5.3
func NativeScanPayload(scanUUID string, scan *database.Scan, out Outcome) (ScanCompletedPayload, bool)
NativeScanPayload builds the payload for one native scan from its row (nil when it does not exist) and the caller's Outcome. ok is false when there is nothing to report: no row, and an Outcome that names neither a failure nor an interrupt.
type Sender ¶
type Sender struct {
// contains filtered or unexported fields
}
Sender posts ScanCompletedPayload to a configured URL with retry on 5xx.
func NewSender ¶
func NewSender(cfg config.WebhookConfig) *Sender
NewSender returns a Sender or nil when the webhook URL is empty. Provider/master-switch gating is the caller's responsibility — this helper only validates the per-channel config.
func (*Sender) Post ¶
func (s *Sender) Post(ctx context.Context, payload ScanCompletedPayload) error
Post sends the payload with retries (5 attempts, exponential backoff on 5xx and network errors). 4xx responses are not retried. Returns the last error encountered when all attempts fail.
func (*Sender) PostAsync ¶
func (s *Sender) PostAsync(payload ScanCompletedPayload)
PostAsync fires the POST in a goroutine and logs failures. Use this from scan-completion paths so the scan does not block on webhook delivery. The delivery is tracked for Drain.