# Validate Webhook Authenticity

> Validate Webhook Authenticity

Source: https://help.boltapp.com/gaming/guide/bolt-webhooks/validate-authenticity

## 1. Configure a Webhook Endpoint [#1-configure-a-webhook-endpoint]

In your [Bolt Merchant account](https://merchant-sandbox.boltapp.com/):

* In the left-side menu, go to **Administration** → **Webhooks**.
* Enter the URL of the endpoint on your server that will receive webhook requests.

## 2. Get Your Signing Secret [#2-get-your-signing-secret]

In your [Bolt Merchant account](https://merchant-sandbox.boltapp.com/):

* In the left-side menu, go to **Administration** → **API**.
* Copy the **Signing Secret**. You will need it to validate requests.

## 3. Validate Incoming Webhooks [#3-validate-incoming-webhooks]

Each webhook request includes a signature in the X-Bolt-Hmac-Sha256 header. To verify the request:

* Take the raw request body.
* Hash it with your **Signing Secret** using HMAC + SHA-256.
* Base64-encode the result.
* Compare it to the signature in the header. If they match, the webhook is valid.

<CodeGroup>
  <CodeBlockTabs defaultValue="TypeScript">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="TypeScript">
        TypeScript
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="JavaScript">
        JavaScript
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="Python">
        Python
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="PHP">
        PHP
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="TypeScript">
      ```typescript
      import { createHmac, timingSafeEqual } from "node:crypto";

      export function isFromBolt(
        rawBody: Buffer,
        headerValue: string,
        signingSecret: string,
      ): boolean {
        const expected = createHmac("sha256", signingSecret).update(rawBody).digest("base64");
        const a = Buffer.from(expected);
        const b = Buffer.from(headerValue);
        // timingSafeEqual throws unless both buffers are the same length.
        return a.length === b.length && timingSafeEqual(a, b);
      }
      ```
    </CodeBlockTab>

    <CodeBlockTab value="JavaScript">
      ```javascript
      const { createHmac, timingSafeEqual } = require("node:crypto");

      function isFromBolt(rawBody, headerValue, signingSecret) {
        const expected = createHmac("sha256", signingSecret).update(rawBody).digest("base64");
        const a = Buffer.from(expected);
        const b = Buffer.from(headerValue);
        // timingSafeEqual throws unless both buffers are the same length.
        return a.length === b.length && timingSafeEqual(a, b);
      }
      ```
    </CodeBlockTab>

    <CodeBlockTab value="Python">
      ```python
      import base64, hashlib, hmac

      def is_from_bolt(raw_body: bytes, header_value: str, signing_secret: str) -> bool:
          digest = hmac.new(signing_secret.encode(), raw_body, hashlib.sha256).digest()
          expected = base64.b64encode(digest).decode()
          return hmac.compare_digest(expected, header_value)
      ```
    </CodeBlockTab>

    <CodeBlockTab value="PHP">
      ```php
      <?php
      function isFromBolt(string $rawBody, string $headerValue, string $signingSecret): bool
      {
          $expected = base64_encode(hash_hmac('sha256', $rawBody, $signingSecret, true));
          return hash_equals($expected, $headerValue);
      }
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

Pass the raw request body exactly as received. Parsing and re-serializing the JSON first changes the bytes, and the signature will not match. `headerValue` is the value of the `X-Bolt-Hmac-Sha256` header; read it the way your framework exposes headers, which is usually lowercased.

<Tip>
  See more information about [Bolt Webhooks](https://help.boltapp.com/developers/webhooks/) for additional information.
</Tip>
