Documentation
¶
Overview ¶
Package webhook verifies GitHub webhook deliveries.
GitHub signs each webhook delivery with HMAC-SHA256 over the raw request body using the secret configured on the webhook. This package verifies the X-Hub-Signature-256 header in constant time and exposes an http.Handler middleware for ergonomic integration.
See https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries.
Index ¶
Examples ¶
Constants ¶
const ( SignatureHeader = "X-Hub-Signature-256" EventHeader = "X-GitHub-Event" DeliveryHeader = "X-GitHub-Delivery" )
Header names GitHub sets on every webhook delivery.
const DefaultMaxPayloadSize int64 = 25 * 1024 * 1024
DefaultMaxPayloadSize matches GitHub's documented 25 MiB delivery cap.
Variables ¶
var ( ErrMissingSecret = errors.New("webhook: secret must not be empty") ErrMissingSignature = errors.New("webhook: missing signature header") ErrInvalidSignatureFormat = errors.New("webhook: invalid signature format") ErrSignatureMismatch = errors.New("webhook: signature mismatch") )
Sentinel errors returned by Verify. Callers can branch with errors.Is.
Functions ¶
func Middleware ¶
Middleware returns net/http middleware that verifies the signature header against secret before invoking next. Failed verifications short-circuit with 401 Unauthorized; bodies larger than the configured cap return 413. The request body is restored for downstream handlers.
An empty secret rejects every delivery with ErrMissingSecret rather than verifying against a zero-length HMAC key, so a misconfigured deployment fails closed and surfaces as failed deliveries instead of accepting forgeries.
Example ¶
Middleware verifies X-Hub-Signature-256 on every delivery and restores the request body before invoking the wrapped handler. Failed verifications short-circuit with 401; oversized bodies return 413.
package main
import (
"log"
"net/http"
"os"
"github.com/jferrl/go-githubauth/webhook"
)
func main() {
secret := []byte(os.Getenv("GITHUB_WEBHOOK_SECRET"))
mux := http.NewServeMux()
mux.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
// The body reaching this handler is already authenticated.
event := r.Header.Get(webhook.EventHeader)
delivery := r.Header.Get(webhook.DeliveryHeader)
log.Printf("received %s delivery=%s", event, delivery)
w.WriteHeader(http.StatusNoContent)
})
log.Fatal(http.ListenAndServe(":8080", webhook.Middleware(secret)(mux)))
}
Output:
func Verify ¶
Verify reports whether signature is a valid HMAC-SHA256 of body using secret. signature must be in GitHub's "sha256=<hex>" form, as delivered in the X-Hub-Signature-256 header. Comparison runs in constant time.
An empty secret returns ErrMissingSecret: HMAC would otherwise accept the zero-length key and treat forged deliveries as authentic.
Example ¶
Verify checks a delivery outside net/http — AWS Lambda, Cloud Run event triggers, or messages consumed from a queue.
package main
import (
"errors"
"fmt"
"os"
"github.com/jferrl/go-githubauth/webhook"
)
func main() {
secret := []byte(os.Getenv("GITHUB_WEBHOOK_SECRET"))
body := []byte(`{"action":"opened"}`)
signature := "sha256=..." // value of the X-Hub-Signature-256 header
err := webhook.Verify(secret, body, signature)
switch {
case err == nil:
// body is trusted from here
case errors.Is(err, webhook.ErrMissingSignature):
fmt.Println("header absent")
case errors.Is(err, webhook.ErrInvalidSignatureFormat):
fmt.Println("malformed header")
case errors.Is(err, webhook.ErrSignatureMismatch):
fmt.Println("wrong secret or tampered body")
}
}
Output:
Types ¶
type MiddlewareOpt ¶
type MiddlewareOpt func(*middlewareConfig)
MiddlewareOpt configures Middleware.
func WithErrorHandler ¶
func WithErrorHandler(fn func(http.ResponseWriter, *http.Request, error)) MiddlewareOpt
WithErrorHandler overrides how verification failures are reported. The default writes 401 Unauthorized (or 413 for oversized bodies) with a short plain-text reason.
func WithMaxPayloadSize ¶
func WithMaxPayloadSize(n int64) MiddlewareOpt
WithMaxPayloadSize overrides the request body size cap. A non-positive value disables the cap, which is not recommended in production.