> ## Documentation Index
> Fetch the complete documentation index at: https://developers.essal.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Verify Webhooks

> Verify the authenticity of Essal webhook payloads using HMAC-SHA256 signature verification.

Every webhook delivery from Essal includes an `X-Essal-Signature` header. You must verify this signature before processing the payload to protect against spoofed requests.

## Signature Format

The header value is a hex-encoded HMAC-SHA256 hash of the raw request body, prefixed with `sha256=`:

```
X-Essal-Signature: sha256=a1b2c3d4e5f6...
```

## Verification

The HMAC is computed using the webhook `secret` returned when you created the webhook subscription.

### Node.js

```js theme={null}
import crypto from "crypto";

function verifySignature(rawBody, signature, secret) {
  const hmac = crypto.createHmac("sha256", secret);
  hmac.update(rawBody);
  const expected = "sha256=" + hmac.digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected, "utf8"),
    Buffer.from(signature, "utf8")
  );
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.headers["x-essal-signature"];
  if (!verifySignature(req.body, sig, process.env.ESSAL_WEBHOOK_SECRET)) {
    return res.status(401).send("Invalid signature");
  }
  const event = JSON.parse(req.body);
  // handle event...
  res.sendStatus(200);
});
```

### Python

```python theme={null}
import hmac, hashlib

def verify_signature(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
```

<Warning>
  Always use a constant-time comparison function (`timingSafeEqual`, `hmac.compare_digest`) to prevent timing attacks. A standard string equality check is not safe.
</Warning>

## Idempotency

Essal may deliver the same event more than once (at-least-once delivery). Use the event `id` field to deduplicate events in your handler — store processed event IDs and skip events you have already handled.
