Overview
Every developer who integrates Fluxa webhooks must implement HMAC-SHA256 signature verification. Getting this wrong means silently accepting forged webhook payloads — a critical security vulnerability. Fluxa currently documents the signature format in prose but provides no reference implementations. This issue produces official verification SDK snippets in five languages, a set of published test vectors (known payload + secret → known signature) that developers can use to validate their implementation, and an interactive verifier in the Fluxa dashboard.
What needs to be built
internal/webhook/ — Signature generation hardening
- Add:
X-Fluxa-Timestamp header (Unix seconds at delivery time) — to prevent replay attacks
- Verification algorithm developers must implement:
- Check
|now - X-Fluxa-Timestamp| < 300 (5-minute tolerance) — reject stale deliveries
- Construct
signed_payload = X-Fluxa-Timestamp + "." + raw_body
- Compute
expected = HMAC-SHA256(signed_payload, webhook_secret)
- Compare
expected to X-Fluxa-Signature using a constant-time comparison
- Update all existing webhook deliveries to include
X-Fluxa-Timestamp
docs/webhook-verification/ — Reference implementations
One file per language, each < 50 lines, production-quality:
verify.ts — TypeScript (Node.js crypto module, no external dependencies)
verify.py — Python (hmac + hashlib stdlib, no external dependencies)
verify.go — Go (crypto/hmac + crypto/sha256 stdlib)
verify.rb — Ruby (openssl stdlib)
verify.php — PHP (hash_hmac + hash_equals)
Each file includes: the verification function, inline comments explaining each step, a usage example, and a note about constant-time comparison and why it matters.
docs/webhook-verification/test-vectors.json
-
10 test vectors: { secret, timestamp, body, expectedSignature } — generated using the Fluxa reference implementation
-
Vectors include: normal payload, empty body, Unicode body, large body (10KB), and a stale timestamp (> 5 minutes old, expected result: rejected)
internal/webhook/ — API
-
POST /v1/webhooks/verify — accepts { secret, timestamp, body, signature }; runs the verification algorithm; returns { valid: boolean, reason: string | null }; rate limited to 60 requests per minute per IP
Dashboard — Webhook endpoint settings page
-
"Verify Signature" tool: paste area for raw request headers (auto-extracts X-Fluxa-Signature and X-Fluxa-Timestamp), paste area for body, secret input (masked), "Verify" button
-
Result: green "Valid" or red "Invalid" with the failure reason
-
"Copy code snippet" button per language
Acceptance criteria
Overview
Every developer who integrates Fluxa webhooks must implement HMAC-SHA256 signature verification. Getting this wrong means silently accepting forged webhook payloads — a critical security vulnerability. Fluxa currently documents the signature format in prose but provides no reference implementations. This issue produces official verification SDK snippets in five languages, a set of published test vectors (known payload + secret → known signature) that developers can use to validate their implementation, and an interactive verifier in the Fluxa dashboard.
What needs to be built
internal/webhook/— Signature generation hardeningX-Fluxa-Timestampheader (Unix seconds at delivery time) — to prevent replay attacks|now - X-Fluxa-Timestamp| < 300(5-minute tolerance) — reject stale deliveriessigned_payload = X-Fluxa-Timestamp + "." + raw_bodyexpected = HMAC-SHA256(signed_payload, webhook_secret)expectedtoX-Fluxa-Signatureusing a constant-time comparisonX-Fluxa-Timestampdocs/webhook-verification/— Reference implementationsOne file per language, each < 50 lines, production-quality:
verify.ts— TypeScript (Node.jscryptomodule, no external dependencies)verify.py— Python (hmac+hashlibstdlib, no external dependencies)verify.go— Go (crypto/hmac+crypto/sha256stdlib)verify.rb— Ruby (opensslstdlib)verify.php— PHP (hash_hmac+hash_equals)Each file includes: the verification function, inline comments explaining each step, a usage example, and a note about constant-time comparison and why it matters.
docs/webhook-verification/test-vectors.json10 test vectors:
{ secret, timestamp, body, expectedSignature }— generated using the Fluxa reference implementationVectors include: normal payload, empty body, Unicode body, large body (10KB), and a stale timestamp (> 5 minutes old, expected result: rejected)
internal/webhook/— APIPOST /v1/webhooks/verify— accepts{ secret, timestamp, body, signature }; runs the verification algorithm; returns{ valid: boolean, reason: string | null }; rate limited to 60 requests per minute per IPDashboard — Webhook endpoint settings page
"Verify Signature" tool: paste area for raw request headers (auto-extracts
X-Fluxa-SignatureandX-Fluxa-Timestamp), paste area for body, secret input (masked), "Verify" buttonResult: green "Valid" or red "Invalid" with the failure reason
"Copy code snippet" button per language
Acceptance criteria
expectedSignaturefor all 10 test vectors — confirmed by running each implementation against the full vector setPOST /v1/webhooks/verifycorrectly returnsvalid: falsewithreason: 'stale_timestamp'for a timestamp 6 minutes in the past==on the signature values)X-Fluxa-Timestampheader is present on all new webhook deliveries — confirmed by inspecting thewebhook_delivery_attemptstable's stored headers429