Webhooks
Receive signed webhooks from Stripe, GitHub, or any HMAC-based provider with one command:
regius make webhook stripe-payment --provider stripe
What It Generates
- Creates
handlers/webhook_<name>.go: a POST-only endpoint that verifies the payload's HMAC signature with thegithub.com/hbarral/regius/webhookpackage before the body is parsed, then decodes it - Mounts the route in
routes-api.goat/api/webhooks/<name>— under/apiso the endpoint reuses the existing CSRF and sanitizer exemptions (signature verification needs the byte-exact raw body) - Appends
WEBHOOK_<NAME>_SECRETto.envwith a generated 32-character secret; existing values are never overwritten, so re-runs stay idempotent - Hyphenated names become valid Go identifiers:
stripe-payment→StripePaymentWebhook
Provider Presets
| Provider | Header | Notes |
|---|---|---|
generic (default) | X-Signature | Plain HMAC-SHA256 digest of the body, hex-encoded |
github | X-Hub-Signature-256 | sha256=<hex> prefix; legacy sha1 available via Options.Hash |
stripe | Stripe-Signature | t=<unix>,v1=<sig> signed payload with 5-minute replay tolerance |
Security Notes
- All signature comparisons are constant time (
hmac.Equal), mirroring the API key middleware - Multiple secrets are supported for rotation: pass extras to the preset call in the generated handler (any-match semantics), then drop the old one once the provider has rotated
- Stale Stripe timestamps are rejected to blunt replay attacks; tune or disable via
Options.TimestampTolerance - The generated handler never logs the unverified payload; errors map to responses:
500unconfigured secret,401bad signature,400missing header / stale timestamp - It responds 2xx fast and leaves TODO markers for typed payloads and idempotent processing (providers retry on any non-2xx)
Manual Verification
import "github.com/hbarral/regius/webhook"
opts := webhook.Stripe(os.Getenv("WEBHOOK_STRIPE_PAYMENT_SECRET"))
payload, err := webhook.Verify(r, opts)
if err != nil {
// webhook.ErrNoSecret / ErrMissingHeader / ErrBadSignature / ErrBadTimestamp
}
On success r.Body is restored, so downstream code can re-read the payload (e.g. ReadJSON).
Environment Variables
One secret per endpoint, appended by the CLI:
#WEBHOOK_STRIPE_PAYMENT_SECRET=