webhook

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: May 15, 2025 License: Apache-2.0 Imports: 12 Imported by: 0

README

Webhook Package

A flexible, thread-safe client for sending webhook HTTP requests with retry and logging capabilities.

Installation

go get github.com/dmitrymomot/gokit/webhook

Overview

The webhook package provides a robust client for sending HTTP webhook requests to external services with configurable retry logic and structured logging. It follows a clean decorator pattern, allowing you to add capabilities like automatic retries and comprehensive logging to a base webhook sender. All components are designed to be thread-safe for concurrent usage.

Features

  • Flexible HTTP webhook sending with support for all common HTTP methods
  • Automatic parameter handling (JSON body or query parameters based on HTTP method)
  • Configurable retry mechanism with exponential backoff and custom retry conditions
  • Structured logging with privacy controls for sensitive data
  • Thread-safe implementation for concurrent webhook requests
  • Decorator pattern for modular extension of functionality
  • Context support for timeouts and cancellation
  • Zero external dependencies beyond the Go standard library

Usage

Basic Request
import (
	"context"
	"fmt"
	"github.com/dmitrymomot/gokit/webhook"
)

func main() {
	// Create a new webhook sender
	sender := webhook.NewWebhookSender()
	
	// Send a POST request with JSON payload
	params := map[string]any{
		"event": "user.created",
		"user_id": 123456,
		"timestamp": time.Now().Unix(),
	}
	
	// Send the webhook request
	resp, err := sender.Send(context.Background(), "https://api.example.com/webhook", params)
	if err != nil {
		// Handle error
		fmt.Printf("Failed to send webhook: %v\n", err)
		return
	}
	
	// Check response status
	if !resp.IsSuccessful() {
		fmt.Printf("Request failed with status %d\n", resp.StatusCode)
		return
	}
	
	// Response body is available as resp.Body ([]byte)
	// Headers are available as resp.Headers (http.Header)
	// Request duration is available as resp.Duration (time.Duration)
}
Using Different HTTP Methods
// GET request with query parameters
// Parameters are automatically converted to a query string
params := map[string]any{
	"term": "golang",
	"limit": 10,
}

resp, err := sender.Send(
	ctx, 
	"https://api.example.com/search", 
	params,
	webhook.WithMethod("GET"),
)
// Sends request to: https://api.example.com/search?term=golang&limit=10

// PUT request with custom headers
resp, err := sender.Send(
	ctx, 
	"https://api.example.com/users/123", 
	userDataMap,
	webhook.WithMethod("PUT"),
	webhook.WithHeader("Authorization", "Bearer token123"),
	webhook.WithHeader("X-Custom-Header", "value"),
)
Retry Decorator
// Create a base webhook sender
baseSender := webhook.NewWebhookSender()

// Add retry capabilities
retrySender := webhook.NewRetryDecorator(
	baseSender,
	webhook.WithRetryCount(3),                // Retry up to 3 times
	webhook.WithRetryDelay(1 * time.Second),  // Start with 1 second delay
	webhook.WithRetryBackoff(),               // Use exponential backoff
	webhook.WithRetryOnServerErrors(),        // Retry on 5xx errors
	webhook.WithRetryOnNetworkErrors(),       // Retry on network failures
)

// Use the retry-enabled sender
resp, err := retrySender.Send(ctx, "https://api.example.com/webhook", params)
Logging Decorator
import (
	"log/slog"
	"os"
)

// Create a logger
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
	Level: slog.LevelInfo,
}))

// Create a base webhook sender
baseSender := webhook.NewWebhookSender()

// Add logging capability with masked sensitive fields
loggedSender := webhook.NewLoggerDecorator(
	baseSender, 
	logger,
	webhook.WithMaskedFields("api_key", "password", "token"),
)

// Use the logging-enabled sender
resp, err := loggedSender.Send(ctx, "https://api.example.com/webhook", params)
// Logs request details (with sensitive fields masked) and response details
Combined Decorators
// Create a base webhook sender
baseSender := webhook.NewWebhookSender()

// Add logging capability first (inner decorator)
loggedSender := webhook.NewLoggerDecorator(
	baseSender,
	logger,
	webhook.WithMaskedFields("password", "token"),
)

// Add retry capability (outer decorator)
sender := webhook.NewRetryDecorator(
	loggedSender,
	webhook.WithRetryCount(3),
	webhook.WithRetryBackoff(),
	webhook.WithRetryOnNetworkErrors(),
)

// Use the fully decorated sender
resp, err := sender.Send(ctx, "https://api.example.com/webhook", params)
// The request will be logged both before sending and after receiving a response
// If the request fails, it will be retried up to 3 times with logging for each attempt
Error Handling
resp, err := sender.Send(ctx, "https://api.example.com/webhook", params)
if err != nil {
	switch {
	case errors.Is(err, webhook.ErrInvalidURL):
		// Handle invalid URL error
	case errors.Is(err, webhook.ErrMarshalParams):
		// Handle parameter marshaling error
	case errors.Is(err, webhook.ErrSendRequest):
		// Handle network or connection error
	case errors.Is(err, webhook.ErrResponseTimeout):
		// Handle timeout error
	default:
		// Handle other errors
	}
	return
}

// Check response status
if !resp.IsSuccessful() {
	// Handle unsuccessful HTTP status code (non 2xx)
	fmt.Printf("Request failed with status %d: %s\n", resp.StatusCode, resp.Body)
	return
}

Best Practices

  1. Error Handling:

    • Always check both the error return value and the response status code
    • Use errors.Is() to check for specific error types
    • Implement proper logging for failed requests for troubleshooting
  2. Security:

    • Use WithMaskedFields() to protect sensitive data in logs
    • Always use HTTPS URLs for webhook endpoints
    • Avoid hardcoding authentication tokens in your code
  3. Reliability:

    • Use the retry decorator for important webhooks or unreliable endpoints
    • Configure reasonable timeout values based on the expected response time
    • Consider implementing circuit breakers for consistently failing endpoints
  4. Performance:

    • Reuse webhook sender instances instead of creating new ones for each request
    • For high-volume webhook sending, consider using goroutines for concurrent requests
    • Use context timeouts to avoid waiting indefinitely for slow endpoints

API Reference

Core Interfaces and Types
// WebhookSender defines the core interface for sending webhooks
type WebhookSender interface {
	Send(ctx context.Context, url string, params any, opts ...RequestOption) (*Response, error)
}

// Request represents a webhook request
type Request struct {
	URL     string
	Method  string
	Headers map[string]string
	Params  any
	Timeout time.Duration
}

// Response contains the HTTP response details
type Response struct {
	StatusCode int
	Body       []byte
	Headers    http.Header
	Duration   time.Duration
	Request    *Request
}
Functions
func NewWebhookSender(opts ...SenderOption) WebhookSender

Creates a new webhook sender with the specified options.

func NewRetryDecorator(sender WebhookSender, opts ...RetryOption) WebhookSender

Wraps a webhook sender with retry capabilities.

func NewLoggerDecorator(sender WebhookSender, logger *slog.Logger, opts ...LoggerOption) WebhookSender

Wraps a webhook sender with logging capabilities.

Methods
func (r *Response) IsSuccessful() bool

Returns true if the response status code is in the 2xx range.

Configuration Options
Sender Options
func WithHTTPClient(client *http.Client) SenderOption
func WithDefaultTimeout(timeout time.Duration) SenderOption
func WithDefaultHeaders(headers map[string]string) SenderOption
func WithDefaultMethod(method string) SenderOption
func WithMaxRetries(retries int) SenderOption
func WithRetryInterval(interval time.Duration) SenderOption
Request Options
func WithMethod(method string) RequestOption
func WithHeader(key, value string) RequestOption
func WithHeaders(headers map[string]string) RequestOption
func WithRequestTimeout(timeout time.Duration) RequestOption
Retry Options
func WithRetryCount(max int) RetryOption
func WithRetryDelay(interval time.Duration) RetryOption
func WithRetryBackoff() RetryOption
func WithRetryOnStatus(statusCodes ...int) RetryOption
func WithRetryOnServerErrors() RetryOption
func WithRetryOnNetworkErrors() RetryOption
func WithRetryLogger(logger *slog.Logger) RetryOption
Logger Options
func WithHideParams() LoggerOption
func WithMaskedFields(fields ...string) LoggerOption
Error Types
var ErrInvalidURL = errors.New("invalid webhook URL")
var ErrInvalidMethod = errors.New("invalid HTTP method")
var ErrMarshalParams = errors.New("failed to marshal request parameters")
var ErrCreateRequest = errors.New("failed to create HTTP request")
var ErrSendRequest = errors.New("failed to send HTTP request")
var ErrReadResponse = errors.New("failed to read HTTP response")
var ErrResponseTimeout = errors.New("webhook request timed out")

Documentation

Index

Constants

View Source
const DefaultMaxRetries = 3

DefaultMaxRetries is the default number of retry attempts

View Source
const DefaultRetryInterval = 500 * time.Millisecond

DefaultRetryInterval is the default interval between retry attempts

Variables

View Source
var (
	// ErrInvalidURL is returned when the webhook URL is invalid or empty
	ErrInvalidURL = errors.New("invalid webhook URL")

	// ErrInvalidMethod is returned when the HTTP method is invalid
	ErrInvalidMethod = errors.New("invalid HTTP method")

	// ErrMarshalParams is returned when there's an error marshaling request parameters
	ErrMarshalParams = errors.New("failed to marshal request parameters")

	// ErrCreateRequest is returned when there's an error creating the HTTP request
	ErrCreateRequest = errors.New("failed to create HTTP request")

	// ErrSendRequest is returned when there's an error sending the HTTP request
	ErrSendRequest = errors.New("failed to send HTTP request")

	// ErrReadResponse is returned when there's an error reading the HTTP response
	ErrReadResponse = errors.New("failed to read HTTP response")

	// ErrResponseTimeout is returned when the webhook request times out
	ErrResponseTimeout = errors.New("webhook request timed out")
)

Error definitions for the webhook package

Functions

This section is empty.

Types

type LoggerDecorator

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

LoggerDecorator wraps a WebhookSender and adds logging functionality

func (*LoggerDecorator) Send

func (l *LoggerDecorator) Send(ctx context.Context, url string, params any, opts ...RequestOption) (*Response, error)

Send sends a webhook with logging before and after the request

type LoggerOption

type LoggerOption func(*LoggerDecorator)

LoggerOption configures the logger decorator

func WithHideParams

func WithHideParams() LoggerOption

WithHideParams prevents request parameters from being logged This is useful for security/privacy when parameters contain sensitive information

func WithMaskedFields

func WithMaskedFields(fields ...string) LoggerOption

WithMaskedFields specifies field names whose values should be masked with asterisks This is useful for logging that fields exist while hiding their sensitive values

type NetworkError

type NetworkError struct {
	Err     error
	Message string
}

NetworkError represents a network-related error when sending a webhook

func (*NetworkError) Error

func (e *NetworkError) Error() string

func (*NetworkError) Unwrap

func (e *NetworkError) Unwrap() error

type Request

type Request struct {
	URL     string
	Method  string
	Headers map[string]string
	Params  any
	Timeout time.Duration
}

Request represents a webhook request

type RequestOption

type RequestOption func(*requestOptions)

RequestOption configures a single webhook request

func WithHeader

func WithHeader(key, value string) RequestOption

WithHeader adds a header to the request

func WithHeaders

func WithHeaders(headers map[string]string) RequestOption

WithHeaders sets multiple headers for the request

func WithMethod

func WithMethod(method string) RequestOption

WithMethod sets the HTTP method for a request

func WithRequestTimeout

func WithRequestTimeout(timeout time.Duration) RequestOption

WithRequestTimeout sets a timeout for this specific request

type Response

type Response struct {
	StatusCode int
	Body       []byte
	Headers    http.Header
	Duration   time.Duration
	Request    *Request // Reference to the original request
}

Response represents a webhook response

func (*Response) IsSuccessful

func (r *Response) IsSuccessful() bool

IsSuccessful returns true if the response status code is in the 2xx range

type RetryDecorator

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

RetryDecorator wraps a WebhookSender and adds retry functionality

func (*RetryDecorator) Send

func (r *RetryDecorator) Send(ctx context.Context, url string, params any, opts ...RequestOption) (*Response, error)

Send sends a webhook with retry logic based on configuration

type RetryOption

type RetryOption func(*RetryDecorator)

RetryOption configures the retry decorator

func WithRetryBackoff

func WithRetryBackoff() RetryOption

WithRetryBackoff enables exponential backoff for retry intervals The interval will double after each retry attempt

func WithRetryCount

func WithRetryCount(max int) RetryOption

WithRetryCount sets the maximum number of retry attempts

func WithRetryDelay

func WithRetryDelay(interval time.Duration) RetryOption

WithRetryDelay sets the interval between retry attempts

func WithRetryLogger

func WithRetryLogger(logger *slog.Logger) RetryOption

WithRetryLogger sets a logger for retry operations

func WithRetryOnNetworkErrors

func WithRetryOnNetworkErrors() RetryOption

WithRetryOnNetworkErrors configures the decorator to retry on network-related errors

func WithRetryOnServerErrors

func WithRetryOnServerErrors() RetryOption

WithRetryOnServerErrors configures the decorator to retry on all 5xx server errors

func WithRetryOnStatus

func WithRetryOnStatus(statusCodes ...int) RetryOption

WithRetryOnStatus adds specific HTTP status codes that should trigger a retry

type SenderOption

type SenderOption func(*webhookSender)

SenderOption configures the webhook sender

func WithDefaultHeaders

func WithDefaultHeaders(headers map[string]string) SenderOption

WithDefaultHeaders sets default headers for all requests

func WithDefaultMethod

func WithDefaultMethod(method string) SenderOption

WithDefaultMethod sets the default HTTP method for all requests

func WithDefaultTimeout

func WithDefaultTimeout(timeout time.Duration) SenderOption

WithDefaultTimeout sets the default timeout for all requests

func WithHTTPClient

func WithHTTPClient(client *http.Client) SenderOption

WithHTTPClient sets a custom HTTP client

func WithMaxRetries

func WithMaxRetries(retries int) SenderOption

WithMaxRetries sets the maximum number of retries for failed requests

func WithRetryInterval

func WithRetryInterval(interval time.Duration) SenderOption

WithRetryInterval sets the interval between retries

type WebhookSender

type WebhookSender interface {
	// Send webhooks with minimal required parameters and optional request options
	Send(ctx context.Context, url string, params any, opts ...RequestOption) (*Response, error)
}

WebhookSender is the interface for sending webhooks

func NewLoggerDecorator

func NewLoggerDecorator(sender WebhookSender, logger *slog.Logger, opts ...LoggerOption) WebhookSender

NewLoggerDecorator creates a new webhook sender with logging capabilities

func NewRetryDecorator

func NewRetryDecorator(sender WebhookSender, opts ...RetryOption) WebhookSender

NewRetryDecorator creates a new webhook sender with retry capabilities

func NewWebhookSender

func NewWebhookSender(opts ...SenderOption) WebhookSender

NewWebhookSender creates a new webhook sender

Jump to

Keyboard shortcuts

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