Skip to main content

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 the github.com/hbarral/regius/webhook package before the body is parsed, then decodes it
  • Mounts the route in routes-api.go at /api/webhooks/<name> — under /api so the endpoint reuses the existing CSRF and sanitizer exemptions (signature verification needs the byte-exact raw body)
  • Appends WEBHOOK_<NAME>_SECRET to .env with 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​

ProviderHeaderNotes
generic (default)X-SignaturePlain HMAC-SHA256 digest of the body, hex-encoded
githubX-Hub-Signature-256sha256=<hex> prefix; legacy sha1 available via Options.Hash
stripeStripe-Signaturet=<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: 500 unconfigured secret, 401 bad signature, 400 missing 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=