Skip to main content
Every webhook delivery includes an X-Xquik-Signature header containing an HMAC-SHA256 signature. Always verify this signature before processing events.

How It Works

  1. Xquik computes sha256= + HMAC-SHA256(webhook secret, raw JSON body)
  2. The result is sent in the X-Xquik-Signature header
  3. Your server recomputes the signature and compares using constant-time comparison

Implementation

# Generate a test signature to verify your implementation
echo -n '{"test":"payload"}' | openssl dgst -sha256 -hmac "your_webhook_secret" | sed 's/.*= /sha256=/'

Security Checklist

Never process webhook payloads without verifying the signature first. An unverified payload could be a spoofed request.
Use timingSafeEqual (Node.js), hmac.compare_digest (Python), or hmac.Equal (Go). String equality (===) is vulnerable to timing attacks.
Compute the HMAC over the raw request body bytes, not a re-serialized JSON object. Re-serialization can alter whitespace or key ordering.
Return 200 within 10 seconds. Process events asynchronously if your handler is slow.

Idempotency

Webhook deliveries can be retried on failure, so your endpoint may receive the same event multiple times. The payload does not include a built-in delivery ID, so compute a hash of the raw request body to deduplicate events.
import { createHash } from "node:crypto";

const processedPayloads = new Set();

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.headers["x-xquik-signature"];
  const payload = req.body.toString();

  if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
    return res.status(401).send("Invalid signature");
  }

  // Deduplicate by hashing the raw payload
  const payloadHash = createHash("sha256").update(payload).digest("hex");

  if (processedPayloads.has(payloadHash)) {
    return res.status(200).send("Already processed");
  }

  processedPayloads.add(payloadHash);

  const event = JSON.parse(payload);
  // Process event...
  handleEvent(event);

  res.status(200).send("OK");
});
The in-memory examples above work for single-process servers. In production, use a persistent store (database, Redis) to track processed payload hashes across restarts and multiple instances.