Skip to main content
Cresora Commerce
Webhooks

Signature Verification

Verify HMAC-SHA256 webhook signatures to authenticate Cresora deliveries.

Every webhook delivery is signed with HMAC-SHA256 using your endpoint's signing secret. Verify this signature before processing any event.

Headers

HeaderValue
X-Cresora-Signaturesha256=<hex-encoded-hmac>
X-Cresora-TimestampUnix timestamp of delivery (seconds)

Verification algorithm

  1. Build the signing payload: timestamp.raw_body
  2. Compute HMAC-SHA256(payload, signing_secret)
  3. Compare to the value in X-Cresora-Signature (constant-time comparison)
  4. Reject if timestamp is older than 5 minutes
Node.js
import crypto from "node:crypto";

function verifyWebhook(req, secret) {
  const timestamp = req.headers["x-cresora-timestamp"];
  const signature = req.headers["x-cresora-signature"];
  const body = req.rawBody; // raw bytes, not parsed JSON

  // Reject stale deliveries (replay protection)
  const ageSeconds = Math.floor(Date.now() / 1000) - Number(timestamp);
  if (ageSeconds > 300) throw new Error("Webhook timestamp too old");

  const payload = `${timestamp}.${body}`;
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    throw new Error("Webhook signature mismatch");
  }
}
Python
import hmac, hashlib, time

def verify_webhook(body: bytes, headers: dict, secret: str):
    timestamp = headers["X-Cresora-Timestamp"]
    signature = headers["X-Cresora-Signature"]

    age = int(time.time()) - int(timestamp)
    if age > 300:
        raise ValueError("Webhook timestamp too old")

    payload = f"{timestamp}.{body.decode()}"
    expected = "sha256=" + hmac.new(
        secret.encode(), payload.encode(), hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        raise ValueError("Webhook signature mismatch")
🔒Security requirement

Always use constant-time comparison (timingSafeEqual / hmac.compare_digest). String equality (===) is vulnerable to timing attacks.

Storing your signing secret

Store the signing secret in your secrets manager (not in code or environment files committed to source control). Rotate it in the Partner Portal if compromised.