---
title: Securing Webhooks
description: Verify Capawesome Cloud webhook signatures to ensure payloads are authentic and untampered, with examples in Node.js and Python.
---

# Securing Webhooks

Your webhook endpoint is a public URL, so anyone who discovers it could send it a forged request. Signing fixes that: when a webhook has a **signing secret** configured (available for the `Raw` format), Capawesome Cloud signs every request so your endpoint can confirm it's genuine.

You set the signing secret when you [create or edit the webhook](setup.md). With it in place, each request includes an `X-Signature` header containing an HMAC-SHA256 signature of the raw request body. Verify this signature on your endpoint to ensure the request is authentic and hasn't been tampered with.

Two details matter for the check to be correct:

- Always compute the HMAC over the **raw request body** — the exact bytes you received, not a re-serialized object, since reserializing can change whitespace or key order and break the signature.
- Use a **constant-time comparison** to avoid leaking information through timing.

=== "Node.js"

    ```js
    import crypto from "node:crypto";

    const secret = "SIGNING_SECRET";
    const digest = Buffer.from(
      crypto.createHmac("sha256", secret).update(request.rawBody).digest("hex"),
      "utf8"
    );
    const signature = Buffer.from(request.get("X-Signature") || "", "utf8");

    if (digest.length !== signature.length || !crypto.timingSafeEqual(digest, signature)) {
      throw new Error("Invalid signature.");
    }
    ```

=== "Python"

    ```python
    import hashlib
    import hmac

    secret = b"SIGNING_SECRET"
    digest = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
    signature = request.headers.get("X-Signature", "")

    if not hmac.compare_digest(digest, signature):
        raise ValueError("Invalid signature.")
    ```

If the signatures don't match, reject the request — return a `4xx` and don't process the payload.
