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 ¶
- Variables
- func VerifySignature(secret string, payload []byte, headers SignatureHeaders, maxAge time.Duration) error
- type BackoffStrategy
- type CircuitBreaker
- type CircuitState
- type CircuitStats
- type DeliveryHook
- type DeliveryResult
- type ExponentialBackoff
- type FixedBackoff
- type LinearBackoff
- type SendOption
- func WithBackoff(strategy BackoffStrategy) SendOption
- func WithBasicRetry(attempts int, interval time.Duration) SendOption
- func WithCircuitBreaker(cb *CircuitBreaker) SendOption
- func WithExponentialRetry(attempts int, initialInterval, maxInterval time.Duration) SendOption
- func WithHTTPClient(client *http.Client) SendOption
- func WithHeader(key, value string) SendOption
- func WithHeaders(headers map[string]string) SendOption
- func WithMaxPayloadSize(size int64) SendOption
- func WithMaxResponseSize(size int64) SendOption
- func WithMaxRetries(n int) SendOption
- func WithNoRetry() SendOption
- func WithOnDelivery(hook DeliveryHook) SendOption
- func WithSignature(secret string) SendOption
- func WithTimeout(timeout time.Duration) SendOption
- type Sender
- type SignatureHeaders
Constants ¶
This section is empty.
Variables ¶
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 ¶
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 ¶
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 ¶
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 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 ¶
NewSenderWithClient creates a webhook sender with a custom HTTP client. This allows for custom transports, proxies, or testing.
type SignatureHeaders ¶
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.