webhookdelivery

package
v3.1.2 Latest Latest
Warning

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

Go to latest
Published: May 21, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package webhookdelivery defines supported-adapter outbound webhook delivery contracts for tenant-scoped API services.

Use Catalog, Dispatcher, EndpointStore, DeliveryStore, SecretResolver, and async worker helpers to sign, dispatch, retry, and replay app-owned webhook deliveries. The package owns provider-neutral contracts and safe headers; it does not choose persistence, tenant mapping, or receiver-specific schemas.

Keep signing secrets and raw payloads out of logs and metrics. Retry classification and replay paths must stay tenant-scoped and fail closed on mismatches.

Index

Constants

View Source
const (
	// AnyEventType subscribes an endpoint to every event type in the catalog.
	AnyEventType = "*"
)

Variables

View Source
var (
	// ErrInvalidEndpoint reports a webhook endpoint with missing or unsafe fields.
	ErrInvalidEndpoint = errors.New("invalid webhook endpoint")
	// ErrInvalidEvent reports a webhook event with missing or malformed fields.
	ErrInvalidEvent = errors.New("invalid webhook event")
	// ErrUnsupportedEvent reports an event type not present in the event catalog.
	ErrUnsupportedEvent = errors.New("unsupported webhook event")
	// ErrUnsafeHeader reports endpoint headers that could carry secrets or break signing.
	ErrUnsafeHeader = errors.New("unsafe webhook header")
	// ErrInvalidDelivery reports a malformed delivery or replay command.
	ErrInvalidDelivery = errors.New("invalid webhook delivery")
	// ErrTenantMismatch reports a job, event, endpoint, or delivery tenant mismatch.
	ErrTenantMismatch = errors.New("webhook tenant mismatch")
	// ErrDeliveryNotFound reports a missing tenant-scoped endpoint or delivery.
	ErrDeliveryNotFound = errors.New("webhook delivery not found")
	// ErrDeliveryFailed reports a delivery attempt that was not accepted by the receiver.
	ErrDeliveryFailed = errors.New("webhook delivery failed")
	// ErrStoreNotConfigured reports missing registry, store, or recorder dependencies.
	ErrStoreNotConfigured = errors.New("webhook delivery store not configured")
)

Functions

func DefaultDeliveryID

func DefaultDeliveryID(event Event, endpoint Endpoint) string

DefaultDeliveryID returns a deterministic idempotency-friendly delivery id for an event/endpoint pair.

func EncodeJobPayload

func EncodeJobPayload(payload JobPayload) ([]byte, error)

EncodeJobPayload validates and encodes the safe async delivery payload.

func EndpointMatches

func EndpointMatches(endpoint Endpoint, event Event) bool

EndpointMatches verifies tenant, disabled state, and event subscription.

func Replay

func Replay(ctx context.Context, store Replayer, cmd ReplayCommand) error

Replay schedules a tenant-scoped delivery for another attempt.

func SafeHeaders

func SafeHeaders(headers http.Header) (http.Header, error)

SafeHeaders validates and clones endpoint static headers. Secret-bearing, hop-by-hop, and signing headers are rejected.

func SafeLabel

func SafeLabel(value string) string

SafeLabel returns a bounded metric/log label.

func ValidateDelivery

func ValidateDelivery(delivery Delivery) error

ValidateDelivery verifies required durable delivery fields.

func ValidateEndpoint

func ValidateEndpoint(endpoint Endpoint, policy EndpointPolicy) error

ValidateEndpoint verifies required endpoint fields, URL policy, subscriptions, and safe static headers.

func ValidateEvent

func ValidateEvent(event Event) error

ValidateEvent verifies a webhook event without applying catalog policy.

Types

type AttemptRecorder

type AttemptRecorder interface {
	RecordAttempt(ctx context.Context, result AttemptResult) error
}

AttemptRecorder records a durable delivery attempt.

type AttemptResult

type AttemptResult struct {
	DeliveryID  string    `json:"delivery_id"`
	TenantID    string    `json:"tenant_id"`
	EndpointID  string    `json:"endpoint_id"`
	EventID     string    `json:"event_id"`
	EventType   string    `json:"event_type"`
	Attempt     int       `json:"attempt"`
	StatusCode  int       `json:"status_code,omitempty"`
	StatusClass string    `json:"status_class"`
	Accepted    bool      `json:"accepted"`
	Retryable   bool      `json:"retryable"`
	Error       string    `json:"error,omitempty"`
	OccurredAt  time.Time `json:"occurred_at"`
}

AttemptResult describes one outbound delivery attempt.

type Catalog

type Catalog struct {
	// contains filtered or unexported fields
}

Catalog is the allow-list of outbound event types a service can publish.

func NewCatalog

func NewCatalog(eventTypes ...string) (Catalog, error)

NewCatalog creates a fail-closed event catalog.

func (Catalog) Allows

func (c Catalog) Allows(eventType string) bool

Allows reports whether the catalog includes eventType.

func (Catalog) ValidateEvent

func (c Catalog) ValidateEvent(event Event) error

ValidateEvent validates required event fields and catalog membership.

type Deliverer

type Deliverer struct {
	// contains filtered or unexported fields
}

Deliverer signs and sends outbound webhook HTTP requests.

func NewDeliverer

func NewDeliverer(cfg DelivererConfig) (*Deliverer, error)

NewDeliverer constructs an HTTP webhook deliverer.

func (*Deliverer) Deliver

func (d *Deliverer) Deliver(ctx context.Context, endpoint Endpoint, event Event, attempt int) (AttemptResult, error)

Deliver signs and sends an event to an endpoint. The returned error and AttemptResult.Error are deliberately payload- and secret-free.

type DelivererConfig

type DelivererConfig struct {
	Client          HTTPDoer
	Clock           func() time.Time
	EndpointPolicy  EndpointPolicy
	SignatureHeader string
	EventIDHeader   string
	TimestampHeader string
	UserAgent       string
	Metrics         MetricsRecorder
}

DelivererConfig configures the HTTP webhook deliverer.

type Delivery

type Delivery struct {
	ID             string        `json:"id"`
	TenantID       string        `json:"tenant_id"`
	EndpointID     string        `json:"endpoint_id"`
	EventID        string        `json:"event_id"`
	EventType      string        `json:"event_type"`
	URL            string        `json:"url"`
	State          DeliveryState `json:"state"`
	Attempt        int           `json:"attempt"`
	NextAt         time.Time     `json:"next_at"`
	LastStatusCode int           `json:"last_status_code,omitempty"`
	LastError      string        `json:"last_error,omitempty"`
	CreatedAt      time.Time     `json:"created_at"`
	UpdatedAt      time.Time     `json:"updated_at"`
}

Delivery is a tenant-scoped durable delivery record.

type DeliveryEnqueuer

type DeliveryEnqueuer interface {
	EnqueueDelivery(ctx context.Context, delivery Delivery, job JobPayload) error
}

DeliveryEnqueuer creates a durable delivery and queues its async job.

type DeliveryObservation

type DeliveryObservation struct {
	EventType   string
	Outcome     string
	StatusClass string
}

DeliveryObservation is a low-cardinality metrics/logging event.

type DeliveryState

type DeliveryState string

DeliveryState describes durable outbound webhook delivery state.

const (
	// StatePending means a delivery is queued for the worker.
	StatePending DeliveryState = "pending"
	// StateLeased means a worker owns the current attempt.
	StateLeased DeliveryState = "leased"
	// StateSucceeded means the receiver accepted the delivery.
	StateSucceeded DeliveryState = "succeeded"
	// StateFailed means the delivery attempt failed but can be retried.
	StateFailed DeliveryState = "failed"
	// StateDeadLetter means retry policy has been exhausted.
	StateDeadLetter DeliveryState = "dead_letter"
)

type Dispatcher

type Dispatcher struct {
	// contains filtered or unexported fields
}

Dispatcher creates durable deliveries for subscribed tenant endpoints.

func NewDispatcher

func NewDispatcher(cfg DispatcherConfig) (*Dispatcher, error)

NewDispatcher constructs a Dispatcher.

func (*Dispatcher) Dispatch

func (d *Dispatcher) Dispatch(ctx context.Context, event Event) ([]Delivery, error)

Dispatch creates pending deliveries and queues async jobs for subscribed tenant endpoints.

type DispatcherConfig

type DispatcherConfig struct {
	Catalog        Catalog
	Endpoints      EndpointRegistry
	Store          DeliveryEnqueuer
	Clock          func() time.Time
	DeliveryIDFunc func(Event, Endpoint) string
	EndpointPolicy EndpointPolicy
}

DispatcherConfig configures a Dispatcher.

type Endpoint

type Endpoint struct {
	ID            string      `json:"id"`
	TenantID      string      `json:"tenant_id"`
	URL           string      `json:"url"`
	SigningSecret []byte      `json:"-"`
	Events        []string    `json:"events"`
	Headers       http.Header `json:"headers,omitempty"`
	Disabled      bool        `json:"disabled,omitempty"`
	CreatedAt     time.Time   `json:"created_at,omitempty"`
	UpdatedAt     time.Time   `json:"updated_at,omitempty"`
}

Endpoint is a tenant-scoped outbound webhook target.

SigningSecret is intentionally omitted from JSON so durable jobs and logs do not accidentally persist raw endpoint secrets.

func (Endpoint) SubscribedTo

func (endpoint Endpoint) SubscribedTo(eventType string) bool

SubscribedTo reports whether the endpoint subscribes to eventType.

type EndpointGetter

type EndpointGetter interface {
	GetEndpoint(ctx context.Context, tenantID, endpointID string) (Endpoint, bool, error)
}

EndpointGetter loads one tenant-scoped endpoint.

type EndpointPolicy

type EndpointPolicy struct {
	AllowInsecureHTTP bool
}

EndpointPolicy configures endpoint validation. HTTPS is required by default; tests and local-only development can explicitly allow HTTP.

type EndpointRegistry

type EndpointRegistry interface {
	ListEndpoints(ctx context.Context, tenantID, eventType string) ([]Endpoint, error)
}

EndpointRegistry lists tenant-scoped endpoints for an event type.

type Event

type Event struct {
	ID         string          `json:"id"`
	TenantID   string          `json:"tenant_id"`
	Type       string          `json:"type"`
	Payload    json.RawMessage `json:"payload"`
	OccurredAt time.Time       `json:"occurred_at,omitempty"`
}

Event is the durable outbound webhook event envelope used by dispatchers and async worker jobs. Payload must be valid JSON.

type HTTPDoer

type HTTPDoer interface {
	Do(*http.Request) (*http.Response, error)
}

HTTPDoer is the subset of http.Client used by Deliverer.

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler loads endpoints, sends jobs, and records attempt metadata for async.Runner.

func NewHandler

func NewHandler(cfg HandlerConfig) (*Handler, error)

NewHandler constructs an async delivery handler.

func (*Handler) Handle

func (h *Handler) Handle(ctx context.Context, job async.Job) error

Handle executes one async delivery job.

type HandlerConfig

type HandlerConfig struct {
	Endpoints EndpointGetter
	Deliverer *Deliverer
	Attempts  AttemptRecorder
}

HandlerConfig configures an async delivery handler.

type JobPayload

type JobPayload struct {
	DeliveryID string `json:"delivery_id"`
	EndpointID string `json:"endpoint_id"`
	Event      Event  `json:"event"`
}

JobPayload is the safe async payload for a delivery worker. It does not carry endpoint secrets; workers load the tenant-scoped endpoint before signing.

func DecodeJobPayload

func DecodeJobPayload(payload []byte) (JobPayload, error)

DecodeJobPayload decodes and validates a worker job payload.

type MetricsRecorder

type MetricsRecorder interface {
	ObserveWebhookDelivery(ctx context.Context, event DeliveryObservation)
}

MetricsRecorder records low-cardinality delivery outcomes.

type MetricsRecorderFunc

type MetricsRecorderFunc func(context.Context, DeliveryObservation)

MetricsRecorderFunc adapts a function to MetricsRecorder.

func (MetricsRecorderFunc) ObserveWebhookDelivery

func (f MetricsRecorderFunc) ObserveWebhookDelivery(ctx context.Context, event DeliveryObservation)

ObserveWebhookDelivery records an outbound webhook event.

type ReplayCommand

type ReplayCommand struct {
	TenantID   string
	DeliveryID string
	NextAt     time.Time
}

ReplayCommand requests tenant-scoped redelivery of a durable delivery.

type Replayer

type Replayer interface {
	ReplayDelivery(ctx context.Context, tenantID, deliveryID string, nextAt time.Time) error
}

Replayer moves a tenant-scoped delivery back into pending state.

type RetryPolicy

type RetryPolicy struct {
	BaseDelay time.Duration
	MaxDelay  time.Duration
}

RetryPolicy computes bounded exponential retry delays.

func (RetryPolicy) NextDelay

func (p RetryPolicy) NextDelay(attempt int) time.Duration

NextDelay returns the retry delay for a one-based attempt number.

Jump to

Keyboard shortcuts

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