Webhook Signing
Verify that incoming requests to your endpoints are genuinely from Fliq using HMAC-SHA256 signatures.
How it works
Every outgoing request from Fliq — both job executions and webhook notifications — includes two signature headers:
X-Fliq-Timestamp: 1774076020
X-Fliq-Signature: v1=661165d836a1ea...
X-Fliq-Timestamp— Unix timestamp (seconds) when the request was signed. Use this to reject stale requests (replay protection).X-Fliq-Signature— HMAC-SHA256 signature prefixed withv1=. The version prefix allows future algorithm upgrades without breaking existing integrations.
Getting your signing secret
Go to Dashboard → Settings → Webhook Signing to view your signing secret. It starts with whsec_. You can rotate it at any time — rotation takes effect immediately.
Signature scheme
The signed payload is a dot-separated string of four fields:
{timestamp}.{METHOD}.{url}.{body}
timestamp— exact value of theX-Fliq-TimestampheaderMETHOD— uppercase HTTP method (e.g.POST)url— full URL of the request, as configured in the jobbody— raw request body, or empty string if none
The HMAC is computed using SHA-256 with your signing secret as the key, then hex-encoded and prefixed with v1=.
Verification examples
Copy the verification function for your language and call it in your request handler before processing the payload.
Python
import hmac, hashlib, time
def verify_fliq_signature(secret, timestamp, method, url, body, signature):
# Reject requests older than 5 minutes
if abs(time.time() - int(timestamp)) > 300:
raise ValueError("Timestamp too old — possible replay attack")
payload = f"{timestamp}.{method}.{url}.{body or ''}"
expected = "v1=" + hmac.new(
secret.encode(), payload.encode(), hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
raise ValueError("Invalid signature")
# Usage in your request handler:
# verify_fliq_signature(
# secret="whsec_...",
# timestamp=request.headers["X-Fliq-Timestamp"],
# method=request.method,
# url=request.url,
# body=request.get_data(as_text=True),
# signature=request.headers["X-Fliq-Signature"],
# )
Node.js
const crypto = require("crypto");
function verifyFliqSignature(secret, timestamp, method, url, body, signature) {
// Reject requests older than 5 minutes
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
throw new Error("Timestamp too old — possible replay attack");
}
const payload = `${timestamp}.${method}.${url}.${body || ""}`;
const expected =
"v1=" + crypto.createHmac("sha256", secret).update(payload).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
throw new Error("Invalid signature");
}
}
// Usage in Express:
// app.post("/webhook", (req, res) => {
// verifyFliqSignature(
// process.env.FLIQ_SIGNING_SECRET,
// req.headers["x-fliq-timestamp"],
// req.method,
// `${req.protocol}://${req.get("host")}${req.originalUrl}`,
// req.body,
// req.headers["x-fliq-signature"],
// );
// // ... handle request
// });
Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"math"
"strconv"
"time"
)
func VerifyFliqSignature(secret, timestamp, method, url, body, signature string) error {
ts, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil {
return fmt.Errorf("invalid timestamp")
}
if math.Abs(float64(time.Now().Unix()-ts)) > 300 {
return fmt.Errorf("timestamp too old")
}
payload := fmt.Sprintf("%s.%s.%s.%s", timestamp, method, url, body)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(payload))
expected := "v1=" + hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(signature)) {
return fmt.Errorf("invalid signature")
}
return nil
}
Best practices
- Always check the timestamp. Reject requests where
X-Fliq-Timestampis more than 5 minutes old to prevent replay attacks. - Use constant-time comparison. Use
hmac.compare_digest(Python),crypto.timingSafeEqual(Node.js), orhmac.Equal(Go) to prevent timing attacks. - Store the secret securely. Use environment variables or a secrets manager — never hardcode it in your source code.
- Rotate periodically. You can rotate your signing secret at any time from the dashboard. In-flight jobs will self-heal via automatic retries.