Agent skill
Flux CD Webhooks Skill
Receive and verify Flux CD webhooks. Use when setting up a Flux notification-controller generic-hmac Provider endpoint, debugging X-Signature verification failures, or handling Flux events by severity (info, error) and reason such as ValidationFailed.
Install this skill
npx skills add hookdeck/webhook-skills --skill flux-cd-webhooks
When to Use This Skill
- How do I receive Flux CD webhooks (notification-controller
generic-hmacProvider)? - How do I verify the Flux CD
X-Signatureheader? - Why does my Flux webhook signature verification fail?
- How do I handle Flux events (
severityinfo/error,reasonlikeValidationFailed)?
Setup note
Use a Flux Provider of type generic-hmac and paste the token from its Secret. Generic providers are unsigned and fail verification.
Verification (core)
Flux signs the raw request body only with HMAC-SHA256, keyed with the token from the Provider's Secret (UTF-8 bytes, used as-is). The X-Signature header is sha256=<hex>; accept the bare hex digest too. No timestamp or id is signed, so there is no replay window to check. Validate the digest format (64 hex characters) before decoding, then compare in constant time. Missing, malformed or mismatched signature: 401.
Node:
const crypto = require('crypto');
function verify(rawBody /* Buffer */, header, secret) {
if (!header || !secret) return false;
const digest = header.startsWith('sha256=') ? header.slice(7) : header;
if (!/^[0-9a-fA-F]{64}$/.test(digest)) return false;
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
return crypto.timingSafeEqual(Buffer.from(digest, 'hex'), expected);
}
Python:
import hmac, hashlib, re
def verify(raw_body: bytes, header: str | None, secret: str) -> bool:
if not header or not secret:
return False
digest = header.removeprefix("sha256=")
if not re.fullmatch(r"[0-9a-fA-F]{64}", digest):
return False
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
return hmac.compare_digest(bytes.fromhex(digest), expected)
Read the raw body as bytes (express.raw({ type: '*/*' }), await request.arrayBuffer(), await request.body()) and parse JSON only after the check.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Types
Flux has no event-name field or header. Every delivery is one Flux Event; branch on severity and reason.
severity | Meaning | Example reason |
|---|---|---|
error | A reconciliation or validation failed | ValidationFailed |
info | Normal progress | ReconciliationSucceeded |
Other body fields: involvedObject (kind, name, namespace, uid), message, metadata, reportingController, reportingInstance, timestamp. Which events arrive is set by the Alert (eventSeverity, eventSources).
For the Event object, see Flux notification Provider docs
Important Headers
| Header | Description |
|---|---|
X-Signature | sha256=<hex> HMAC-SHA256 of the raw body (generic-hmac Providers only) |
Gotk-Component | Controller that raised the event, e.g. kustomize-controller |
Content-Type | application/json |
Requests are POST. Hookdeck verifies sha256 only; Flux notes the default hash "may change in the future", so a delivery signed with another hash fails.
Environment Variables
FLUX_CD_WEBHOOK_SECRET=your_provider_secret_token # `token` key of the Secret in Provider spec.secretRef
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 flux-cd --path /webhooks/flux-cd
Reference Materials
- references/overview.md - Flux notification concepts, Event shape
- references/setup.md - Provider, Secret and Alert configuration
- references/verification.md - Signature verification details