> ## Documentation Index
> Fetch the complete documentation index at: https://pigeonpost-developer.27communication.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Verify signatures

> Check that a webhook call really came from your engine.

Each call has an `X-Email-Engine-Signature` header:

```
X-Email-Engine-Signature: sha256=5f8a…
```

The part after `sha256=` is the HMAC-SHA256 of the **raw request body**, keyed with the endpoint's `secret`, in hex. You get the `secret` when you [create the endpoint](/webhooks/overview#create-an-endpoint). Compute it yourself and compare. Reject the call if they differ.

<Warning>
  Use the raw body exactly as received. If you parse the JSON and encode it again, the bytes change and the signature won't match.
</Warning>

<CodeGroup>
  ```php PHP (Laravel) theme={null}
  $expected = 'sha256='.hash_hmac('sha256', $request->getContent(), config('services.email_engine.webhook_secret'));

  if (! hash_equals($expected, (string) $request->header('X-Email-Engine-Signature'))) {
      abort(401);
  }
  ```

  ```js Node.js (Express) theme={null}
  import crypto from "node:crypto";

  app.post("/webhooks/email", express.raw({ type: "application/json" }), (req, res) => {
    const expected = "sha256=" + crypto.createHmac("sha256", process.env.EE_WEBHOOK_SECRET).update(req.body).digest("hex");
    const given = req.get("X-Email-Engine-Signature") || "";
    if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body); // { object: "Event", id, type, data, … }
    res.sendStatus(200);
  });
  ```

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

  def is_valid(body: bytes, header: str, secret: str) -> bool:
      expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
      return hmac.compare_digest(expected, header or "")
  ```
</CodeGroup>

Use a constant-time comparison (`hash_equals`, `timingSafeEqual`, `compare_digest`), as above.

You can also add your own header when you [create the endpoint](/webhooks/overview#create-an-endpoint), such as `Authorization: Bearer …`, and check it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.