webhook

package
v0.5.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: MIT Imports: 14 Imported by: 0

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

View Source
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

func AgenticEvent(status string) (event, completeness, stopReason string)

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

func Drain(timeout time.Duration) (undelivered int)

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

func NativeEvent(status, errMsg, stopReason string) (event, outStatus string, terminal bool)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL