webhooks

package
v2.2.2 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: AGPL-3.0 Imports: 9 Imported by: 0

Documentation

Overview

Package webhooks defines outbound webhook delivery: event-bus events are persisted, fanned out to administrator-approved HTTP targets, delivered with retries and a dead-letter queue, and every attempt is logged so an operator can search what was sent where, when, and what came back.

Target URLs are an exfiltration vector, so a target never receives a delivery until an administrator approves it, and every change to where a target points (URL, custom headers) sends it back to pending.

Community Edition ships a no-op implementation that answers every query with ErrEnterpriseFeature. Enterprise Edition registers the real one via the factory in this package.

Index

Constants

View Source
const (
	FormatCSV  = "csv"
	FormatJSON = "json"
)

Export formats.

View Source
const (
	DefaultPageSize = 50
	MaxPageSize     = 500
	MaxExportRows   = 50000
	MaxBulkReplay   = 500
	// SearchWindow bounds a free-text search that names no start date.
	SearchWindow = 30 * 24 * time.Hour
)

Paging bounds, shared by the API and the enterprise implementation.

Variables

View Source
var (
	ErrNotFound     = errors.New("webhook resource not found")
	ErrConflict     = errors.New("webhook target was modified by someone else; reload and retry")
	ErrInvalidState = errors.New("operation not allowed in the target's current state")
	ErrURLPolicy    = errors.New("target URL is not allowed by the webhook URL policy")
	ErrTemplate     = errors.New("payload template is invalid")
	ErrSameApprover = errors.New("a target must be approved by a different administrator than the one who created it")
	ErrNotApproved  = errors.New("target is not approved")
	ErrValidation   = errors.New("invalid webhook input")
	ErrDisabled     = errors.New("webhooks are disabled by configuration")
)

Errors shared by both editions. Handlers map them onto HTTP statuses.

View Source
var ErrEnterpriseFeature = errors.New("webhooks are an Enterprise Edition feature - visit https://tyk.io/ai-studio/pricing for more information")

ErrEnterpriseFeature is returned by Community Edition for every operation.

View Source
var StatsWindows = map[string]time.Duration{
	"1h":  time.Hour,
	"24h": 24 * time.Hour,
	"7d":  7 * 24 * time.Hour,
}

StatsWindows are the windows accepted by Stats.

Functions

func ExportContentType

func ExportContentType(format string) string

ExportContentType is the Content-Type for an export in the given format.

func IsEnterpriseAvailable

func IsEnterpriseAvailable() bool

IsEnterpriseAvailable reports whether the enterprise implementation is linked in.

func RegisterEnterpriseFactory

func RegisterEnterpriseFactory(f FactoryFunc)

RegisterEnterpriseFactory is called by the enterprise implementation's init().

Types

type Actor

type Actor struct {
	UserID uint
	Email  string
	Name   string
}

Actor is the administrator performing an operation, taken from the authenticated request. It is recorded on the target and in the audit trail.

type DeliveryDetail

type DeliveryDetail struct {
	Delivery models.WebhookDelivery          `json:"delivery"`
	Attempts []models.WebhookDeliveryAttempt `json:"attempts"`
	Event    *models.WebhookEvent            `json:"event,omitempty"`
	Target   *models.WebhookTargetResponse   `json:"target,omitempty"`
}

DeliveryDetail is one delivery with everything an operator needs to understand it: the attempts, the event that caused it and the target.

type DeliveryPage

type DeliveryPage struct {
	Deliveries []models.WebhookDelivery `json:"deliveries"`
	Total      int64                    `json:"total"`
	Page       int                      `json:"page"`
	PageSize   int                      `json:"page_size"`
}

DeliveryPage is one page of deliveries plus the total for the filter. RenderedPayload is omitted from list rows; GetDelivery returns it.

type DeliveryQuery

type DeliveryQuery struct {
	TargetID string
	Topic    string
	Statuses []string
	EventID  string
	Kind     string
	Start    time.Time
	End      time.Time
	// Search matches a delivery or event id exactly, or a case-insensitive
	// substring of the target URL, topic or last error. Response snippets
	// are not searched. A search with no Start is bounded to the last
	// SearchWindow so it never scans the whole table.
	Search string

	Page     int // 1-based
	PageSize int
	SortAsc  bool // default newest first
}

DeliveryQuery filters the delivery log. Zero values mean "no filter".

type Deps

type Deps struct {
	DB *gorm.DB
	// Bus is the in-process event bus. Nil means events are not available on
	// this node (targets can still be managed; nothing is ingested).
	Bus eventbridge.Bus
	// Notifier sends in-app/email notifications to administrators. May be nil.
	Notifier Notifier
	// Audit returns the audit trail service. It is a getter because the audit
	// service is created by the API after this service exists; nil is fine.
	Audit  func() audit.Service
	Config config.WebhooksConfig
	// NodeID identifies this Studio node in delivery leases and logs.
	NodeID string
	// Version is reported in the User-Agent of outbound requests.
	Version string
}

Deps is everything the enterprise implementation needs from the host.

type FactoryFunc

type FactoryFunc func(deps Deps) Service

FactoryFunc builds a webhooks service. The enterprise submodule registers one from its init() when built with -tags enterprise.

type Notifier

type Notifier interface {
	NotifyDirect(notificationID, notifType, title, content string, userFlags uint) error
}

Notifier is the slice of the notification service the feature needs. It is declared here rather than importing services to avoid the import cycle (services imports this package). *services.NotificationService satisfies it.

type Preset

type Preset struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Body        string `json:"body"`
}

Preset is a built-in payload template.

type PreviewInput

type PreviewInput struct {
	TemplatePreset string `json:"template_preset"`
	TemplateBody   string `json:"template_body"`
	Topic          string `json:"topic"`
}

PreviewInput renders a template against a sample event without saving.

type PreviewResult

type PreviewResult struct {
	Rendered string `json:"rendered"`
	Valid    bool   `json:"valid"`
	Error    string `json:"error,omitempty"`
}

PreviewResult is the rendered sample and whether it is valid JSON.

type ReplayRequest

type ReplayRequest struct {
	TargetID string   `json:"target_id"`
	IDs      []string `json:"ids"`
	Max      int      `json:"max"`
}

ReplayRequest selects dead letters to replay: explicit IDs, or every dead letter for a target (optionally all targets) up to Max.

type ReplayResult

type ReplayResult struct {
	Replayed int `json:"replayed"`
	Skipped  int `json:"skipped"`
}

ReplayResult counts what a bulk replay did.

type RotatedSecret

type RotatedSecret struct {
	SigningSecret      string    `json:"signing_secret"`
	PreviousValidUntil time.Time `json:"previous_valid_until"`
}

RotatedSecret is the result of RotateSecret: the new secret, shown once, and how long the previous secret keeps producing a second signature.

type Service

type Service interface {
	// Targets
	ListTargets(ctx context.Context, f TargetFilter) (*TargetList, error)
	GetTarget(ctx context.Context, id string) (*TargetDetail, error)
	// CreateTarget stores a pending target. The returned secret is shown once.
	CreateTarget(ctx context.Context, actor Actor, in TargetInput) (*models.WebhookTargetResponse, string, error)
	// UpdateTarget applies a patch under optimistic locking. repended reports
	// whether the change sent an approved target back to pending.
	UpdateTarget(ctx context.Context, actor Actor, id string, lockVersion int, p TargetPatch) (view *models.WebhookTargetResponse, repended bool, err error)
	DeleteTarget(ctx context.Context, actor Actor, id string) error
	ApproveTarget(ctx context.Context, actor Actor, id string, note string) (*models.WebhookTargetResponse, error)
	RejectTarget(ctx context.Context, actor Actor, id string, reason string) (*models.WebhookTargetResponse, error)
	RevokeTarget(ctx context.Context, actor Actor, id string, reason string) (*models.WebhookTargetResponse, error)
	PauseTarget(ctx context.Context, actor Actor, id string) (*models.WebhookTargetResponse, error)
	ResumeTarget(ctx context.Context, actor Actor, id string) (*models.WebhookTargetResponse, error)
	RotateSecret(ctx context.Context, actor Actor, id string) (*RotatedSecret, error)
	// SendTest enqueues a synthetic event for one approved target and returns the delivery ID.
	SendTest(ctx context.Context, actor Actor, id string, topic string) (string, error)

	// Templates and topics
	PreviewTemplate(ctx context.Context, in PreviewInput) (*PreviewResult, error)
	ListPresets() []Preset
	ListTopics(ctx context.Context) (*TopicList, error)

	// Deliveries
	ListDeliveries(ctx context.Context, q DeliveryQuery) (*DeliveryPage, error)
	GetDelivery(ctx context.Context, id string) (*DeliveryDetail, error)
	// ReplayDelivery enqueues a new delivery for the same event and target and returns its ID.
	ReplayDelivery(ctx context.Context, actor Actor, id string, reRender bool) (string, error)
	CancelDelivery(ctx context.Context, actor Actor, id string) error
	ReplayDeadLetters(ctx context.Context, actor Actor, req ReplayRequest) (*ReplayResult, error)
	Stats(ctx context.Context, window string) (*Stats, error)
	// Export streams matching deliveries (at most MaxExportRows) as CSV or
	// JSON to w, batch by batch, so memory stays flat for large logs.
	// Errors before the first write are returned; a failure mid-stream
	// leaves a truncated document and is also returned.
	Export(ctx context.Context, q DeliveryQuery, format string, w io.Writer) error

	// Lifecycle
	// Cleanup deletes rows past the retention windows. Returns rows removed.
	Cleanup(ctx context.Context) (int64, error)
	Status() Status
	// Stop drains in-flight deliveries and stops background workers.
	Stop()
}

Service is the webhooks contract shared by both editions.

func NewCommunityService

func NewCommunityService() Service

NewCommunityService returns the always-refusing stub regardless of which implementation is linked in. Callers that need a placeholder rather than a configured service (the API's fallback for tests that never call InitWebhooks) use this so an Enterprise build does not construct, and log about, a second disabled enterprise service.

func NewService

func NewService(deps Deps) Service

NewService returns the enterprise implementation when registered, otherwise the community no-op.

type Stats

type Stats struct {
	Window                 string           `json:"window"`
	ByStatus               map[string]int64 `json:"by_status"`
	ByTarget               []TargetStats    `json:"by_target"`
	QueueDepth             int64            `json:"queue_depth"`
	OldestQueuedAgeSeconds int64            `json:"oldest_queued_age_seconds"`
}

Stats aggregates deliveries over a window for the overview strip.

type Status

type Status struct {
	Available bool `json:"available"`
	Enabled   bool `json:"enabled"`
	// DisabledReason explains why Enabled is false when the operator did not
	// switch the feature off (e.g. the secrets encryption key is missing).
	DisabledReason string `json:"disabled_reason,omitempty"`
	BusConnected   bool   `json:"bus_connected"`
	WorkerEnabled  bool   `json:"worker_enabled"`
	Workers        int    `json:"workers"`
	NodeID         string `json:"node_id,omitempty"`
	Dialect        string `json:"dialect,omitempty"`

	QueueDepth     int64 `json:"queue_depth"`
	Retrying       int64 `json:"retrying"`
	InFlight       int64 `json:"in_flight"`
	DeadLettered   int64 `json:"dead_lettered"`
	PendingTargets int64 `json:"pending_targets"`
	// DroppedEvents counts bus events this node could not persist even after
	// spilling to memory. Non-zero means at-least-once was violated.
	DroppedEvents int64      `json:"dropped_events"`
	LastIngestAt  *time.Time `json:"last_ingest_at,omitempty"`
}

Status describes the running feature so the UI can explain itself.

type TargetDetail

type TargetDetail struct {
	Target models.WebhookTargetResponse `json:"target"`
	Stats  TargetStats                  `json:"stats"`
}

TargetDetail is one target with its delivery statistics.

type TargetFilter

type TargetFilter struct {
	Status string
	Search string // case-insensitive substring over name, url, description
}

TargetFilter narrows ListTargets. Zero values mean "no filter".

type TargetInput

type TargetInput struct {
	Name           string            `json:"name"`
	Description    string            `json:"description"`
	URL            string            `json:"url"`
	TopicFilters   []string          `json:"topic_filters"`
	TemplatePreset string            `json:"template_preset"`
	TemplateBody   string            `json:"template_body"`
	Headers        map[string]string `json:"headers"`
	MaxConcurrency int               `json:"max_concurrency"`
	// SigningSecret is optional; a random one is generated when empty.
	SigningSecret string `json:"signing_secret"`
}

TargetInput creates a target.

type TargetList

type TargetList struct {
	Targets      []models.WebhookTargetResponse `json:"targets"`
	PendingCount int64                          `json:"pending_count"`
}

TargetList is every matching target plus the pending count for the badge.

type TargetPatch

type TargetPatch struct {
	Name           *string            `json:"name"`
	Description    *string            `json:"description"`
	URL            *string            `json:"url"`
	TopicFilters   *[]string          `json:"topic_filters"`
	TemplatePreset *string            `json:"template_preset"`
	TemplateBody   *string            `json:"template_body"`
	Headers        *map[string]string `json:"headers"`
	MaxConcurrency *int               `json:"max_concurrency"`
}

TargetPatch updates a target. Nil fields are left unchanged. Changing URL or Headers on an approved target sends it back to pending.

type TargetStats

type TargetStats struct {
	TargetID     string  `json:"target_id"`
	Name         string  `json:"name"`
	Queued       int64   `json:"queued"`
	Retrying     int64   `json:"retrying"`
	InFlight     int64   `json:"in_flight"`
	Succeeded    int64   `json:"succeeded"`
	Failed       int64   `json:"failed"` // dead-lettered + cancelled
	DeadLettered int64   `json:"dead_lettered"`
	P50Ms        float64 `json:"p50_ms"`
	P95Ms        float64 `json:"p95_ms"`
}

TargetStats summarises delivery outcomes for one target.

type TopicList

type TopicList struct {
	Known []string `json:"known"`
	Seen  []string `json:"seen"`
}

TopicList is what the target editor offers: the platform's system topics plus every topic actually observed on the bus recently.

Jump to

Keyboard shortcuts

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