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
- Variables
- func ComputeHMAC(payload, secret []byte, alg HashAlgorithm) string
- func ContextWithEvent(ctx context.Context, event *RawEvent) context.Context
- func ContextWithPayload(ctx context.Context, payload []byte) context.Context
- func DrainBody(r *http.Request, maxSize int64) ([]byte, error)
- func ExtractSignature(header, prefix string) string
- func ParseUnixTimestamp(s string) (time.Time, error)
- func PayloadFromContext(ctx context.Context) ([]byte, bool)
- func ReadBody(r *http.Request, maxSize int64) ([]byte, error)
- func ResetBody(r *http.Request, body []byte)
- func SignPayload(payload []byte, secret string) string
- func SignPayloadWithTimestamp(timestamp int64, payload []byte, secret string) string
- func VerifyFunc(verifier Verifier, handler http.HandlerFunc) http.HandlerFunc
- func VerifyHMAC(payload, secret []byte, signature string, alg HashAlgorithm) bool
- func VerifyMiddleware(verifier Verifier) func(http.Handler) http.Handler
- func VerifyTimestamp(ts time.Time, tolerance time.Duration) error
- func WriteError(w http.ResponseWriter, statusCode int, err error)
- func WriteSuccess(w http.ResponseWriter, message string)
- type BatchResult
- type BatchSender
- type ContextKey
- type DeliveryResult
- type Dispatcher
- func (d *Dispatcher) Dispatch(ctx context.Context, event Event) []BatchResult
- func (d *Dispatcher) DispatchAsync(ctx context.Context, event Event, results chan<- BatchResult)
- func (d *Dispatcher) GetSubscription(id string) (*Subscription, bool)
- func (d *Dispatcher) ListSubscriptions() []*Subscription
- func (d *Dispatcher) Subscribe(sub *Subscription)
- func (d *Dispatcher) Unsubscribe(id string) bool
- type Event
- type GitHubVerifier
- type Group
- type HMACConfig
- type HMACVerifier
- type Handler
- type HandlerFunc
- type HashAlgorithm
- type Payload
- type RawEvent
- type Response
- type Router
- func (r *Router) Clear()
- func (r *Router) Group(prefix string) *Group
- func (r *Router) Handle(ctx context.Context, event *RawEvent) error
- func (r *Router) HandleFunc() http.HandlerFunc
- func (r *Router) On(pattern string, handler Handler)
- func (r *Router) OnHTTP(pattern string, handler HandlerFunc)
- func (r *Router) Routes() []string
- func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)
- type RouterConfig
- type Sender
- type SenderConfig
- type ShopifyVerifier
- type SlackVerifier
- type StripeVerifier
- type Subscription
- type TwilioVerifier
- type Verifier
- type VerifyRequest
Constants ¶
const MaxBodySize = 10 * 1024 * 1024
MaxBodySize is the default maximum request body size (10MB).
const TimestampTolerance = 5 * time.Minute
TimestampTolerance is the default tolerance for timestamp verification.
Variables ¶
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.
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 ¶
ContextWithEvent returns a new context with the webhook event.
func ContextWithPayload ¶
ContextWithPayload returns a new context with the raw webhook payload.
func DrainBody ¶
DrainBody reads the request body and replaces it with a replayable reader. This allows the body to be read multiple times.
func ExtractSignature ¶
ExtractSignature extracts a signature from a header value with optional prefix. For example: "sha256=abc123" returns "abc123" with prefix "sha256=".
func ParseUnixTimestamp ¶
ParseUnixTimestamp parses a Unix timestamp string.
func PayloadFromContext ¶
PayloadFromContext retrieves the raw webhook payload from the context.
func SignPayload ¶
SignPayload signs a payload using HMAC-SHA256 and returns the signature.
func SignPayloadWithTimestamp ¶
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 ¶
VerifyMiddleware returns an HTTP middleware that verifies webhook signatures.
func VerifyTimestamp ¶
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.
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) 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 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 ¶
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 ¶
EventFromContext retrieves the webhook event from the context.
func ParseEvent ¶
ParseEvent parses an event from JSON data.
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 (*Router) HandleFunc ¶
func (r *Router) HandleFunc() http.HandlerFunc
HandleFunc returns an http.HandlerFunc for the router.
func (*Router) On ¶
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.
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 (*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.