AJ ONeal 4abac2a0df
feat(auth/xhubsig): X-Hub-Signature HMAC webhook verification + HTTP middleware
Verify X-Hub-Signature-256 (and SHA-1) webhook signatures. Middleware
buffers and re-exposes the body for downstream handlers. Errors honor
Accept header: TSV default (text/plain for browsers), JSON, CSV, or
Markdown — three fields (error, description, hint) with pseudocode hints.
2026-04-13 17:04:45 -06:00
..

xhubsig

Verify X-Hub-Signature-256 HMAC-SHA256 webhook signatures. HTTP middleware included.

go get github.com/therootcompany/golib/auth/xhubsig

Middleware

Wrap any http.Handler. Verified body is buffered and re-readable by the next handler.

x := xhubsig.New(webhookSecret)
mux.Handle("POST /webhook", x.Require(handleWebhook))

Require both SHA-256 and SHA-1 (all must pass):

x := xhubsig.New(webhookSecret, xhubsig.SHA256, xhubsig.SHA1)

Accept either SHA-256 or SHA-1 (at least one must be present; all present must pass):

x := xhubsig.New(webhookSecret, xhubsig.SHA256, xhubsig.SHA1)
x.AcceptAny = true

Raise the body limit (default 256 KiB):

x.Limit = 1 << 20 // 1 MiB

Sign / Verify

Compute a signature (for sending or testing):

sig := xhubsig.Sign(xhubsig.SHA256, secret, body)
// → "sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17"

Verify a signature directly:

err := xhubsig.Verify(xhubsig.SHA256, secret, body, r.Header.Get("X-Hub-Signature-256"))
if errors.Is(err, xhubsig.ErrMissingSignature) { ... }
if errors.Is(err, xhubsig.ErrInvalidSignature) { ... }

Signature format: sha256=<hex hmac-sha256 of raw request body> using the webhook secret as the HMAC key. sha256 is the default algorithm.

Error responses

Errors honor the Accept header; Content-Type matches. Default is TSV.

Accept Format
text/tab-separated-values vertical key-value TSV (default)
text/html text/plain TSV (browser-safe)
application/json JSON object
text/csv vertical key-value CSV
text/markdown pipe table

TSV example (missing_signature):

field	value
error	missing_signature
description	No valid signature header was found.
hint	X-Hub-Signature-256 is required. `X-Hub-Signature-256: sha256=hex(hmac_sha256(secret, body))`

JSON example:

{
  "error": "missing_signature",
  "description": "No valid signature header was found.",
  "hint": "X-Hub-Signature-256 is required.\n`X-Hub-Signature-256: sha256=hex(hmac_sha256(secret, body))`"
}

Error codes: missing_signature, invalid_signature, body_too_large.

License

CC0-1.0. Public domain.