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 with v1=. 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 the X-Fliq-Timestamp header
  • METHOD — uppercase HTTP method (e.g. POST)
  • url — full URL of the request, as configured in the job
  • body — 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-Timestamp is more than 5 minutes old to prevent replay attacks.
  • Use constant-time comparison. Use hmac.compare_digest (Python), crypto.timingSafeEqual (Node.js), or hmac.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.