webhook

package
v0.3.5 Latest Latest
Warning

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

Go to latest
Published: Feb 17, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package webhook provides reliable HTTP webhook delivery with automatic retries and circuit breaking.

The package handles JSON marshaling, HMAC signatures, exponential backoff, and connection pooling to deliver webhooks reliably to remote endpoints. It's designed for production use with proper error handling, timeout management, and observability hooks.

Basic Usage

Send a webhook with default settings:

sender := webhook.NewSender()

event := map[string]any{
	"type":      "user.created",
	"user_id":   "123",
	"timestamp": time.Now().Unix(),
}

ctx := context.Background()
err := sender.Send(ctx, "https://api.example.com/webhooks", event)
if err != nil {
	log.Printf("Webhook delivery failed: %v", err)
}

Configuration Options

Configure timeout, retries, and signatures:

err := sender.Send(ctx, webhookURL, event,
	webhook.WithTimeout(30*time.Second),
	webhook.WithMaxRetries(3),
	webhook.WithSignature("your-webhook-secret"),
)

Backoff Strategies

Use custom backoff strategy:

backoff := webhook.ExponentialBackoff{
	InitialInterval: 500 * time.Millisecond,
	MaxInterval:     30 * time.Second,
	Multiplier:      2.0,
	JitterFactor:    0.1,
}

err := sender.Send(ctx, webhookURL, event,
	webhook.WithBackoff(backoff),
)

Circuit Breaker

Protect failing endpoints:

cb := webhook.NewCircuitBreaker(5, 2, 30*time.Second)

err := sender.Send(ctx, webhookURL, event,
	webhook.WithCircuitBreaker(cb),
)

if errors.Is(err, webhook.ErrCircuitOpen) {
	log.Println("Circuit breaker protecting endpoint")
}

Signature Verification

Generate and verify signatures:

// Generate signature
payload, _ := json.Marshal(event)
sigHeaders, err := webhook.SignPayload("secret", payload)
if err != nil {
	return err
}

// Extract from HTTP headers
headerMap := map[string]string{
	"X-Webhook-Signature": r.Header.Get("X-Webhook-Signature"),
	"X-Webhook-Timestamp": r.Header.Get("X-Webhook-Timestamp"),
}
extractedSig, err := webhook.ExtractSignatureHeaders(headerMap)

// Verify signature with 5 minute tolerance
err = webhook.VerifySignature("secret", payload, extractedSig, 5*time.Minute)
if err != nil {
	http.Error(w, "Invalid signature", http.StatusUnauthorized)
	return
}

Monitoring

Track delivery attempts:

err := sender.Send(ctx, webhookURL, event,
	webhook.WithOnDelivery(func(result webhook.DeliveryResult) {
		log.Printf("Attempt %d: success=%t, status=%d, duration=%v",
			result.Attempt, result.Success, result.StatusCode, result.Duration)
	}),
)

Error Types

The package defines specific error types for different failure modes:

  • ErrInvalidURL: URL is malformed or uses unsupported scheme
  • ErrInvalidPayload: Payload is empty or exceeds size limit
  • ErrTimeout: Request exceeded timeout
  • ErrCircuitOpen: Circuit breaker is protecting the endpoint
  • ErrPermanentFailure: 4xx HTTP status (won't retry)
  • ErrTemporaryFailure: Network or 5xx error (will retry)
  • ErrWebhookDeliveryFailed: All retry attempts exhausted
  • ErrInvalidConfiguration: Invalid setup or parameters

Core Types

Main types provided by this package:

  • Sender: Main type for sending webhooks with retries and circuit breaking
  • CircuitBreaker: Prevents hammering failing endpoints
  • DeliveryResult: Information about a single delivery attempt
  • SignatureHeaders: HMAC-SHA256 signature data for webhook authentication
  • BackoffStrategy: Interface for custom retry delay strategies (ExponentialBackoff, LinearBackoff, FixedBackoff)

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrWebhookDeliveryFailed = errors.New("webhook: delivery failed")
	ErrInvalidConfiguration  = errors.New("webhook: invalid configuration")
	ErrPermanentFailure      = errors.New("webhook: permanent failure")
	ErrTemporaryFailure      = errors.New("webhook: temporary failure")
	ErrCircuitOpen           = errors.New("webhook: circuit breaker is open")
	ErrInvalidPayload        = errors.New("webhook: invalid payload")
	ErrInvalidURL            = errors.New("webhook: invalid URL")
	ErrTimeout               = errors.New("webhook: request timeout")
)

Domain errors for webhook operations, designed for error wrapping and classification.

Error classification strategy:

  • Configuration errors: Invalid setup or parameters (fail fast)
  • Delivery errors: Network, timeout, or HTTP failures (may retry)
  • Circuit breaker: Protection mechanism when endpoint consistently fails

Functions

func VerifySignature

func VerifySignature(secret string, payload []byte, headers SignatureHeaders, maxAge time.Duration) error

VerifySignature validates webhook authenticity and prevents replay attacks. Uses constant-time comparison and timestamp validation for security.

Types

type BackoffStrategy

type BackoffStrategy interface {
	// NextInterval returns the next backoff duration based on the attempt number.
	// Attempt starts at 1 for the first retry.
	NextInterval(attempt int) time.Duration
}

BackoffStrategy defines the interface for calculating retry delays. Implementations should be safe for concurrent use.

func DefaultBackoffStrategy

func DefaultBackoffStrategy() BackoffStrategy

DefaultBackoffStrategy returns production-ready exponential backoff.

type CircuitBreaker

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

CircuitBreaker implements a simple circuit breaker pattern to prevent hammering failed endpoints. Safe for concurrent use.

func NewCircuitBreaker

func NewCircuitBreaker(failureThreshold, successThreshold int, recoveryTimeout time.Duration) *CircuitBreaker

NewCircuitBreaker creates a circuit breaker with the given configuration. Default values provide reasonable protection for most webhook scenarios.

func (*CircuitBreaker) Allow

func (cb *CircuitBreaker) Allow() bool

Allow checks if a request should be allowed through the circuit breaker.

func (*CircuitBreaker) RecordFailure

func (cb *CircuitBreaker) RecordFailure()

RecordFailure records a failed request and may open the circuit.

func (*CircuitBreaker) RecordSuccess

func (cb *CircuitBreaker) RecordSuccess()

RecordSuccess records a successful request and may close the circuit.

func (*CircuitBreaker) Reset

func (cb *CircuitBreaker) Reset()

Reset resets the circuit breaker to closed state.

func (*CircuitBreaker) State

func (cb *CircuitBreaker) State() CircuitState

State returns the current state, accounting for automatic transitions.

func (*CircuitBreaker) Stats

func (cb *CircuitBreaker) Stats() CircuitStats

Stats returns the current statistics of the circuit breaker.

type CircuitState

type CircuitState int

CircuitState represents the current state of the circuit breaker.

const (
	// CircuitClosed allows requests to pass through.
	CircuitClosed CircuitState = iota
	// CircuitOpen blocks all requests.
	CircuitOpen
	// CircuitHalfOpen allows one request to test if the service has recovered.
	CircuitHalfOpen
)

func (CircuitState) String

func (s CircuitState) String() string

String returns the string representation of the circuit state.

type CircuitStats

type CircuitStats struct {
	LastFailureTime time.Time
	State           string
	Failures        int
	SuccessCount    int
}

CircuitStats provides visibility into circuit breaker state for monitoring.

type DeliveryHook

type DeliveryHook func(result DeliveryResult)

DeliveryHook is called after each delivery attempt.

type DeliveryResult

type DeliveryResult struct {
	Error      error
	Duration   time.Duration
	StatusCode int
	Attempt    int
	Success    bool
}

DeliveryResult contains information about a webhook delivery attempt.

type ExponentialBackoff

type ExponentialBackoff struct {
	InitialInterval time.Duration
	MaxInterval     time.Duration
	Multiplier      float64
	JitterFactor    float64
}

ExponentialBackoff implements exponential backoff with jitter. Jitter prevents thundering herd when multiple clients retry simultaneously.

func (ExponentialBackoff) NextInterval

func (e ExponentialBackoff) NextInterval(attempt int) time.Duration

NextInterval calculates exponential backoff with jitter to prevent coordinated retry storms. Formula: min(InitialInterval * (Multiplier ^ (attempt-1)) * (1 +/- JitterFactor), MaxInterval)

type FixedBackoff

type FixedBackoff struct {
	Interval time.Duration
}

FixedBackoff implements a constant delay between retries.

func (FixedBackoff) NextInterval

func (f FixedBackoff) NextInterval(attempt int) time.Duration

NextInterval always returns the same interval regardless of attempt number.

type LinearBackoff

type LinearBackoff struct {
	Interval    time.Duration
	MaxInterval time.Duration
}

LinearBackoff implements simple linear backoff. Provides predictable retry intervals that increase linearly.

func (LinearBackoff) NextInterval

func (l LinearBackoff) NextInterval(attempt int) time.Duration

NextInterval returns linearly increasing delays. Formula: min(Interval * attempt, MaxInterval)

type SendOption

type SendOption func(*sendOptions)

SendOption is a functional option for configuring webhook sends.

func WithBackoff

func WithBackoff(strategy BackoffStrategy) SendOption

WithBackoff sets the backoff strategy for retries. Default is exponential backoff with jitter.

func WithBasicRetry

func WithBasicRetry(attempts int, interval time.Duration) SendOption

WithBasicRetry configures simple retry behavior with fixed intervals.

func WithCircuitBreaker

func WithCircuitBreaker(cb *CircuitBreaker) SendOption

WithCircuitBreaker enables circuit breaker protection for the endpoint. Reuse the same instance per endpoint to track failure state across requests.

func WithExponentialRetry

func WithExponentialRetry(attempts int, initialInterval, maxInterval time.Duration) SendOption

WithExponentialRetry configures exponential backoff with jitter.

func WithHTTPClient

func WithHTTPClient(client *http.Client) SendOption

WithHTTPClient sets a custom HTTP client for the request. Useful for custom transports, proxies, or testing.

func WithHeader

func WithHeader(key, value string) SendOption

WithHeader adds a custom header to the webhook request. Standard headers like Content-Type are set automatically.

func WithHeaders

func WithHeaders(headers map[string]string) SendOption

WithHeaders adds multiple custom headers to the webhook request.

func WithMaxPayloadSize

func WithMaxPayloadSize(size int64) SendOption

WithMaxPayloadSize sets the maximum allowed payload size in bytes. Default is 10MB if not specified. Set to 0 to disable the limit.

func WithMaxResponseSize

func WithMaxResponseSize(size int64) SendOption

WithMaxResponseSize sets the maximum response body size to read in bytes. Default is 64KB if not specified. This limits memory usage when reading error responses.

func WithMaxRetries

func WithMaxRetries(n int) SendOption

WithMaxRetries sets the maximum number of retry attempts. Default is 3 if not specified. Set to 0 to disable retries.

func WithNoRetry

func WithNoRetry() SendOption

WithNoRetry disables all retry attempts.

func WithOnDelivery

func WithOnDelivery(hook DeliveryHook) SendOption

WithOnDelivery sets a callback that's invoked after each delivery attempt. Useful for logging, metrics, or custom retry logic.

func WithSignature

func WithSignature(secret string) SendOption

WithSignature enables HMAC-SHA256 request signing with the given secret. Adds X-Webhook-Signature, X-Webhook-Timestamp, and X-Webhook-ID headers.

func WithTimeout

func WithTimeout(timeout time.Duration) SendOption

WithTimeout sets the HTTP request timeout. Default is 10 seconds if not specified.

type Sender

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

Sender provides reliable webhook delivery with retries and circuit breaking. Zero value is not usable; use NewSender to create instances.

func NewSender

func NewSender() *Sender

NewSender creates a webhook sender with default HTTP client. Connection pooling is configured for high-throughput scenarios.

func NewSenderWithClient

func NewSenderWithClient(client *http.Client) *Sender

NewSenderWithClient creates a webhook sender with a custom HTTP client. This allows for custom transports, proxies, or testing.

func (*Sender) Send

func (s *Sender) Send(ctx context.Context, webhookURL string, data any, opts ...SendOption) error

Send delivers a webhook payload to the specified URL with retry logic. The payload is marshaled to JSON and sent as a POST request. Options control timeout, retries, signing, and other behavior.

type SignatureHeaders

type SignatureHeaders struct {
	Signature string
	ID        string
	Timestamp int64
}

SignatureHeaders contains the standard webhook signature headers. Follows security patterns used by Stripe, GitHub, and other major webhook providers.

func ExtractSignatureHeaders

func ExtractSignatureHeaders(headers map[string]string) (SignatureHeaders, error)

ExtractSignatureHeaders extracts webhook signature data from HTTP headers. Handles common case variations for compatibility across HTTP implementations.

func SignPayload

func SignPayload(secret string, payload []byte) (SignatureHeaders, error)

SignPayload creates an HMAC-SHA256 signature for webhook authentication. Timestamp binding prevents replay attacks. Signature format: HMAC-SHA256(secret, timestamp + "." + payload)

func (SignatureHeaders) Headers

func (s SignatureHeaders) Headers() map[string]string

Headers returns the signature headers as a map for easy HTTP header setting.

Jump to

Keyboard shortcuts

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