webhook

package
v0.1.36 Latest Latest
Warning

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

Go to latest
Published: Feb 25, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package webhook provides utilities for handling incoming webhooks with signature verification and sending outgoing webhooks with automatic retries.

Incoming webhooks:

// Verify GitHub webhook signature
verifier := webhook.NewGitHubVerifier("webhook-secret")
if err := verifier.Verify(r); err != nil {
    http.Error(w, "Invalid signature", http.StatusUnauthorized)
    return
}

Outgoing webhooks:

// Send webhook with automatic retries
sender := webhook.NewSender(webhook.SenderConfig{
    SigningSecret: "your-secret",
})
err := sender.Send(ctx, "https://example.com/webhook", webhook.Event{
    Type: "order.created",
    Data: order,
})

Event routing:

router := webhook.NewRouter()
router.On("order.created", handleOrderCreated)
router.On("order.*", handleAllOrderEvents)
router.HandleFunc(w, r) // Use as HTTP handler

Index

Constants

View Source
const MaxBodySize = 10 * 1024 * 1024

MaxBodySize is the default maximum request body size (10MB).

View Source
const TimestampTolerance = 5 * time.Minute

TimestampTolerance is the default tolerance for timestamp verification.

Variables

View Source
var (
	ErrInvalidSignature    = errors.New("webhook: invalid signature")
	ErrMissingSignature    = errors.New("webhook: missing signature header")
	ErrTimestampExpired    = errors.New("webhook: timestamp expired")
	ErrMissingTimestamp    = errors.New("webhook: missing timestamp")
	ErrInvalidPayload      = errors.New("webhook: invalid payload")
	ErrDeliveryFailed      = errors.New("webhook: delivery failed")
	ErrNoHandlerFound      = errors.New("webhook: no handler found for event type")
	ErrMaxRetriesExceeded  = errors.New("webhook: max retries exceeded")
	ErrInvalidEventType    = errors.New("webhook: invalid event type")
	ErrRequestBodyTooLarge = errors.New("webhook: request body too large")
)

Common errors returned by webhook operations.

View Source
var StandardHeaders = map[string]string{
	"Content-Type": "application/json",
	"User-Agent":   "Waffle-Webhook/1.0",
}

StandardHeaders are the standard headers set on outgoing webhook requests.

Functions

func ComputeHMAC

func ComputeHMAC(payload, secret []byte, alg HashAlgorithm) string

ComputeHMAC computes an HMAC signature for the given payload.

func ContextWithEvent

func ContextWithEvent(ctx context.Context, event *RawEvent) context.Context

ContextWithEvent returns a new context with the webhook event.

func ContextWithPayload

func ContextWithPayload(ctx context.Context, payload []byte) context.Context

ContextWithPayload returns a new context with the raw webhook payload.

func DrainBody

func DrainBody(r *http.Request, maxSize int64) ([]byte, error)

DrainBody reads the request body and replaces it with a replayable reader. This allows the body to be read multiple times.

func ExtractSignature

func ExtractSignature(header, prefix string) string

ExtractSignature extracts a signature from a header value with optional prefix. For example: "sha256=abc123" returns "abc123" with prefix "sha256=".

func ParseUnixTimestamp

func ParseUnixTimestamp(s string) (time.Time, error)

ParseUnixTimestamp parses a Unix timestamp string.

func PayloadFromContext

func PayloadFromContext(ctx context.Context) ([]byte, bool)

PayloadFromContext retrieves the raw webhook payload from the context.

func ReadBody

func ReadBody(r *http.Request, maxSize int64) ([]byte, error)

ReadBody reads and returns the request body, enforcing a size limit.

func ResetBody

func ResetBody(r *http.Request, body []byte)

ResetBody resets the request body to allow re-reading.

func SignPayload

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

SignPayload signs a payload using HMAC-SHA256 and returns the signature.

func SignPayloadWithTimestamp

func SignPayloadWithTimestamp(timestamp int64, payload []byte, secret string) string

SignPayloadWithTimestamp signs a payload with a timestamp prefix. This is the format used by Stripe and similar services.

func VerifyFunc

func VerifyFunc(verifier Verifier, handler http.HandlerFunc) http.HandlerFunc

VerifyFunc returns an http.HandlerFunc that verifies signatures and calls the handler.

func VerifyHMAC

func VerifyHMAC(payload, secret []byte, signature string, alg HashAlgorithm) bool

VerifyHMAC verifies an HMAC signature.

func VerifyMiddleware

func VerifyMiddleware(verifier Verifier) func(http.Handler) http.Handler

VerifyMiddleware returns an HTTP middleware that verifies webhook signatures.

func VerifyTimestamp

func VerifyTimestamp(ts time.Time, tolerance time.Duration) error

VerifyTimestamp checks if a timestamp is within the allowed tolerance.

func WriteError

func WriteError(w http.ResponseWriter, statusCode int, err error)

WriteError writes an error response.

func WriteSuccess

func WriteSuccess(w http.ResponseWriter, message string)

WriteSuccess writes a success response.

Types

type BatchResult

type BatchResult struct {
	URL     string
	Results []DeliveryResult
	Error   error
}

BatchResult contains results for a batch send operation.

type BatchSender

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

BatchSender sends webhooks to multiple endpoints.

func NewBatchSender

func NewBatchSender(sender *Sender, concurrency int) *BatchSender

NewBatchSender creates a new batch webhook sender.

func (*BatchSender) SendToAll

func (b *BatchSender) SendToAll(ctx context.Context, urls []string, event Event) []BatchResult

SendToAll sends an event to multiple URLs concurrently.

type ContextKey

type ContextKey string

ContextKey is a type for context keys used by this package.

const (
	// ContextKeyEvent is the context key for the parsed webhook event.
	ContextKeyEvent ContextKey = "webhook_event"

	// ContextKeyPayload is the context key for the raw webhook payload.
	ContextKeyPayload ContextKey = "webhook_payload"

	// ContextKeyDeliveryID is the context key for the delivery ID.
	ContextKeyDeliveryID ContextKey = "webhook_delivery_id"
)

type DeliveryResult

type DeliveryResult struct {
	// Success indicates if the delivery was successful.
	Success bool

	// StatusCode is the HTTP status code received.
	StatusCode int

	// ResponseBody is the response body (truncated if large).
	ResponseBody string

	// Duration is how long the request took.
	Duration time.Duration

	// Attempt is which attempt this was (1-based).
	Attempt int

	// Error is the error if delivery failed.
	Error error

	// Timestamp is when this attempt was made.
	Timestamp time.Time
}

DeliveryResult contains the result of a webhook delivery attempt.

type Dispatcher

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

Dispatcher manages webhook subscriptions and dispatches events.

func NewDispatcher

func NewDispatcher(sender *Sender) *Dispatcher

NewDispatcher creates a new webhook dispatcher.

func (*Dispatcher) Dispatch

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

Dispatch sends an event to all matching subscriptions.

func (*Dispatcher) DispatchAsync

func (d *Dispatcher) DispatchAsync(ctx context.Context, event Event, results chan<- BatchResult)

DispatchAsync sends an event to all matching subscriptions asynchronously. Results are sent to the provided channel.

func (*Dispatcher) GetSubscription

func (d *Dispatcher) GetSubscription(id string) (*Subscription, bool)

GetSubscription returns a subscription by ID.

func (*Dispatcher) ListSubscriptions

func (d *Dispatcher) ListSubscriptions() []*Subscription

ListSubscriptions returns all subscriptions.

func (*Dispatcher) Subscribe

func (d *Dispatcher) Subscribe(sub *Subscription)

Subscribe adds a new subscription.

func (*Dispatcher) Unsubscribe

func (d *Dispatcher) Unsubscribe(id string) bool

Unsubscribe removes a subscription.

type Event

type Event struct {
	// ID is a unique identifier for this event.
	ID string `json:"id"`

	// Type is the event type (e.g., "order.created", "user.updated").
	Type string `json:"type"`

	// Timestamp is when the event occurred.
	Timestamp time.Time `json:"timestamp"`

	// Data is the event payload.
	Data any `json:"data"`

	// Metadata contains additional event metadata.
	Metadata map[string]string `json:"metadata,omitempty"`
}

Event represents a webhook event.

func NewEvent

func NewEvent(eventType string, data any) Event

NewEvent creates a new event with a generated ID and current timestamp.

type GitHubVerifier

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

GitHubVerifier verifies GitHub webhook signatures. GitHub uses HMAC-SHA256 with the signature in the X-Hub-Signature-256 header prefixed with "sha256=".

func NewGitHubVerifier

func NewGitHubVerifier(secret string) *GitHubVerifier

NewGitHubVerifier creates a new GitHub webhook verifier.

func (*GitHubVerifier) Verify

func (v *GitHubVerifier) Verify(r *http.Request) error

Verify verifies a GitHub webhook signature.

func (*GitHubVerifier) VerifyPayload

func (v *GitHubVerifier) VerifyPayload(payload []byte, signature string) error

VerifyPayload verifies the signature for a raw payload.

func (*GitHubVerifier) WithMaxBodySize

func (v *GitHubVerifier) WithMaxBodySize(size int64) *GitHubVerifier

WithMaxBodySize sets the maximum body size.

type Group

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

Group creates a handler group with a common prefix.

func (*Group) On

func (g *Group) On(pattern string, handler Handler)

On registers a handler with the group's prefix.

func (*Group) OnHTTP

func (g *Group) OnHTTP(pattern string, handler HandlerFunc)

OnHTTP registers an HTTP handler with the group's prefix.

type HMACConfig

type HMACConfig struct {
	Secret      string
	Algorithm   HashAlgorithm
	Header      string
	Prefix      string
	MaxBodySize int64
}

HMACConfig configures the HMAC verifier.

type HMACVerifier

type HMACVerifier struct {
	// Secret is the shared secret for HMAC computation.
	Secret []byte

	// Algorithm is the hash algorithm to use.
	Algorithm HashAlgorithm

	// Header is the HTTP header containing the signature.
	Header string

	// Prefix is an optional prefix to strip from the signature (e.g., "sha256=").
	Prefix string

	// MaxBodySize is the maximum allowed request body size.
	MaxBodySize int64
}

HMACVerifier verifies HMAC signatures.

func NewHMACVerifier

func NewHMACVerifier(cfg HMACConfig) *HMACVerifier

NewHMACVerifier creates a new HMAC signature verifier.

func (*HMACVerifier) Verify

func (v *HMACVerifier) Verify(r *http.Request) error

Verify verifies the webhook signature from an HTTP request.

func (*HMACVerifier) VerifyPayload

func (v *HMACVerifier) VerifyPayload(payload []byte, signature string) error

VerifyPayload verifies the webhook signature for a raw payload.

type Handler

type Handler func(ctx context.Context, event *RawEvent) error

Handler is a function that handles a webhook event.

func TypedHandler

func TypedHandler[T any](handler func(ctx context.Context, event *RawEvent, data T) error) Handler

TypedHandler creates a handler that parses the event data into a specific type.

type HandlerFunc

type HandlerFunc func(w http.ResponseWriter, r *http.Request, event *RawEvent)

HandlerFunc is an HTTP handler function for webhooks.

func TypedHTTPHandler

func TypedHTTPHandler[T any](handler func(w http.ResponseWriter, r *http.Request, event *RawEvent, data T)) HandlerFunc

TypedHTTPHandler creates an HTTP handler that parses the event data into a specific type.

type HashAlgorithm

type HashAlgorithm string

HashAlgorithm represents a hash algorithm for HMAC signatures.

const (
	SHA256 HashAlgorithm = "sha256"
	SHA512 HashAlgorithm = "sha512"
	SHA1   HashAlgorithm = "sha1"
)

type Payload

type Payload struct {
	// Event is the webhook event.
	Event RawEvent `json:"event"`

	// DeliveryID is the unique ID for this delivery attempt.
	DeliveryID string `json:"delivery_id,omitempty"`

	// Attempt is the delivery attempt number (1-based).
	Attempt int `json:"attempt,omitempty"`
}

Payload represents the full webhook request payload.

func ParsePayload

func ParsePayload(data []byte) (*Payload, error)

ParsePayload parses a webhook payload from JSON data.

type RawEvent

type RawEvent struct {
	// ID is a unique identifier for this event.
	ID string `json:"id"`

	// Type is the event type.
	Type string `json:"type"`

	// Timestamp is when the event occurred.
	Timestamp time.Time `json:"timestamp"`

	// Data is the raw JSON event payload.
	Data json.RawMessage `json:"data"`

	// Metadata contains additional event metadata.
	Metadata map[string]string `json:"metadata,omitempty"`
}

RawEvent represents a webhook event with raw JSON data.

func EventFromContext

func EventFromContext(ctx context.Context) (*RawEvent, bool)

EventFromContext retrieves the webhook event from the context.

func ParseEvent

func ParseEvent(data []byte) (*RawEvent, error)

ParseEvent parses an event from JSON data.

func (*RawEvent) ParseData

func (e *RawEvent) ParseData(v any) error

ParseData parses the raw event data into the provided type.

type Response

type Response struct {
	Success bool   `json:"success"`
	Message string `json:"message,omitempty"`
	Error   string `json:"error,omitempty"`
}

Response represents a standard webhook response.

type Router

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

Router routes incoming webhook events to handlers based on event type.

func NewRouter

func NewRouter(cfg RouterConfig) *Router

NewRouter creates a new webhook router.

func (*Router) Clear

func (r *Router) Clear()

Clear removes all registered handlers.

func (*Router) Group

func (r *Router) Group(prefix string) *Group

Group creates a new handler group with a prefix.

func (*Router) Handle

func (r *Router) Handle(ctx context.Context, event *RawEvent) error

Handle processes an incoming webhook event.

func (*Router) HandleFunc

func (r *Router) HandleFunc() http.HandlerFunc

HandleFunc returns an http.HandlerFunc for the router.

func (*Router) On

func (r *Router) On(pattern string, handler Handler)

On registers a handler for an event type pattern. Patterns can include wildcards:

  • "order.created" - exact match
  • "order.*" - matches any event starting with "order."
  • "*" - matches all events

func (*Router) OnHTTP

func (r *Router) OnHTTP(pattern string, handler HandlerFunc)

OnHTTP registers an HTTP handler for an event type pattern.

func (*Router) Routes

func (r *Router) Routes() []string

Routes returns a list of registered patterns.

func (*Router) ServeHTTP

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP implements http.Handler.

type RouterConfig

type RouterConfig struct {
	// Verifier is used to verify incoming webhook signatures.
	// If nil, signatures are not verified.
	Verifier Verifier

	// MaxBodySize is the maximum allowed request body size.
	// Default: 10MB
	MaxBodySize int64

	// ErrorHandler is called when an error occurs.
	// If nil, a default error response is sent.
	ErrorHandler func(w http.ResponseWriter, r *http.Request, err error)

	// EventParser parses the webhook payload into an event.
	// If nil, the default JSON parser is used.
	EventParser func([]byte) (*RawEvent, error)

	// BeforeHandler is called before any event handler.
	// Can be used for logging, metrics, or additional validation.
	BeforeHandler func(ctx context.Context, event *RawEvent) error

	// AfterHandler is called after event handlers complete.
	// Can be used for logging or cleanup.
	AfterHandler func(ctx context.Context, event *RawEvent, err error)
}

RouterConfig configures the webhook router.

type Sender

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

Sender sends outgoing webhooks with automatic retries.

func NewSender

func NewSender(cfg SenderConfig) *Sender

NewSender creates a new webhook sender.

func (*Sender) Send

func (s *Sender) Send(ctx context.Context, url string, event Event) error

Send sends a webhook event to the specified URL.

func (*Sender) SendRaw

func (s *Sender) SendRaw(ctx context.Context, url string, payload []byte) error

SendRaw sends a raw payload to the specified URL.

func (*Sender) SendWithResults

func (s *Sender) SendWithResults(ctx context.Context, url string, event Event) ([]DeliveryResult, error)

SendWithResults sends a webhook event and returns all delivery attempt results.

type SenderConfig

type SenderConfig struct {
	// SigningSecret is the secret used to sign outgoing webhooks.
	// If empty, webhooks are sent without signatures.
	SigningSecret string

	// SignatureHeader is the header name for the signature.
	// Default: "X-Webhook-Signature"
	SignatureHeader string

	// TimestampHeader is the header name for the timestamp.
	// Default: "X-Webhook-Timestamp"
	TimestampHeader string

	// HTTPClient is the HTTP client to use.
	// If nil, a default client with reasonable timeouts is used.
	HTTPClient *http.Client

	// Timeout is the timeout for each delivery attempt.
	// Default: 30 seconds
	Timeout time.Duration

	// MaxRetries is the maximum number of retry attempts.
	// Default: 3
	MaxRetries int

	// RetryBackoff is the initial backoff duration between retries.
	// Default: 1 second
	RetryBackoff time.Duration

	// MaxBackoff is the maximum backoff duration.
	// Default: 1 minute
	MaxBackoff time.Duration

	// BackoffMultiplier is the multiplier for exponential backoff.
	// Default: 2.0
	BackoffMultiplier float64

	// Headers are additional headers to include in all requests.
	Headers map[string]string

	// UserAgent is the User-Agent header value.
	// Default: "Waffle-Webhook/1.0"
	UserAgent string

	// OnDelivery is called after each delivery attempt.
	// Can be used for logging or metrics.
	OnDelivery func(url string, result DeliveryResult)
}

SenderConfig configures the webhook sender.

type ShopifyVerifier

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

ShopifyVerifier verifies Shopify webhook signatures. Shopify uses HMAC-SHA256 with base64 encoding.

func NewShopifyVerifier

func NewShopifyVerifier(secret string) *ShopifyVerifier

NewShopifyVerifier creates a new Shopify webhook verifier.

func (*ShopifyVerifier) Verify

func (v *ShopifyVerifier) Verify(r *http.Request) error

Verify verifies a Shopify webhook signature.

func (*ShopifyVerifier) VerifyPayload

func (v *ShopifyVerifier) VerifyPayload(payload []byte, signature string) error

VerifyPayload verifies the Shopify signature for a raw payload.

type SlackVerifier

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

SlackVerifier verifies Slack webhook signatures. Slack uses a similar scheme to Stripe with timestamps.

func NewSlackVerifier

func NewSlackVerifier(signingSecret string) *SlackVerifier

NewSlackVerifier creates a new Slack webhook verifier.

func (*SlackVerifier) Verify

func (v *SlackVerifier) Verify(r *http.Request) error

Verify verifies a Slack webhook signature.

func (*SlackVerifier) VerifyPayload

func (v *SlackVerifier) VerifyPayload(payload []byte, signature string) error

VerifyPayload verifies with separate signature and timestamp.

func (*SlackVerifier) WithTolerance

func (v *SlackVerifier) WithTolerance(d time.Duration) *SlackVerifier

WithTolerance sets the timestamp tolerance.

type StripeVerifier

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

StripeVerifier verifies Stripe webhook signatures. Stripe uses a custom signature scheme with timestamps to prevent replay attacks.

func NewStripeVerifier

func NewStripeVerifier(secret string) *StripeVerifier

NewStripeVerifier creates a new Stripe webhook verifier.

func (*StripeVerifier) Verify

func (v *StripeVerifier) Verify(r *http.Request) error

Verify verifies a Stripe webhook signature.

func (*StripeVerifier) VerifyPayload

func (v *StripeVerifier) VerifyPayload(payload []byte, signature string) error

VerifyPayload verifies the Stripe signature for a raw payload.

func (*StripeVerifier) WithMaxBodySize

func (v *StripeVerifier) WithMaxBodySize(size int64) *StripeVerifier

WithMaxBodySize sets the maximum body size.

func (*StripeVerifier) WithTolerance

func (v *StripeVerifier) WithTolerance(d time.Duration) *StripeVerifier

WithTolerance sets the timestamp tolerance.

type Subscription

type Subscription struct {
	// ID is a unique identifier for this subscription.
	ID string `json:"id"`

	// URL is the endpoint to deliver webhooks to.
	URL string `json:"url"`

	// Events is a list of event types to subscribe to.
	// Use "*" to subscribe to all events.
	Events []string `json:"events"`

	// Secret is the signing secret for this subscription.
	Secret string `json:"secret,omitempty"`

	// Active indicates if the subscription is active.
	Active bool `json:"active"`

	// Metadata contains additional subscription metadata.
	Metadata map[string]string `json:"metadata,omitempty"`

	// CreatedAt is when the subscription was created.
	CreatedAt time.Time `json:"created_at"`

	// UpdatedAt is when the subscription was last updated.
	UpdatedAt time.Time `json:"updated_at"`
}

Subscription represents a webhook subscription.

func (*Subscription) Matches

func (s *Subscription) Matches(eventType string) bool

Matches returns true if the subscription matches the given event type.

type TwilioVerifier

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

TwilioVerifier verifies Twilio webhook signatures.

func NewTwilioVerifier

func NewTwilioVerifier(authToken string) *TwilioVerifier

NewTwilioVerifier creates a new Twilio webhook verifier.

func (*TwilioVerifier) Verify

func (v *TwilioVerifier) Verify(r *http.Request) error

Verify verifies a Twilio webhook signature.

func (*TwilioVerifier) VerifyPayload

func (v *TwilioVerifier) VerifyPayload(payload []byte, signature string) error

VerifyPayload performs basic signature verification.

type Verifier

type Verifier interface {
	// Verify verifies the webhook signature from an HTTP request.
	Verify(r *http.Request) error

	// VerifyPayload verifies the webhook signature for a raw payload.
	VerifyPayload(payload []byte, signature string) error
}

Verifier is the interface for webhook signature verification.

type VerifyRequest

type VerifyRequest struct {
	// Body is the raw request body.
	Body []byte

	// Signature is the signature from the request.
	Signature string

	// Timestamp is the timestamp from the request (if available).
	Timestamp time.Time

	// Headers contains relevant headers.
	Headers http.Header
}

VerifyRequest contains the parsed and verified webhook request.

Jump to

Keyboard shortcuts

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