# How to Verify a Webhook

> Verify HMAC signatures on inbound Bolt webhooks.

Source: https://help.boltapp.com/api-reference/guides/verify-webhooks

All requests made by Bolt to your ecommerce Merchant Callback API will be signed to ensure the authenticity of our requests. Your implementation should always verify the signature to make sure that it's always Bolt calling your endpoint.

Bolt signs the payload and includes the HMAC signature in the request header X-Bolt-Hmac-Sha256. There are two ways to verify the payload with this signature.

## Step 1: Get Signing Secret [#step-1-get-signing-secret]

1. Log in to your Bolt [Merchant Dashboard](https://merchant.boltapp.com).
2. Navigate to **Administration > API**.
3. Scroll to **Signing Secret**.

## Step 2: Use Secret for Verification [#step-2-use-secret-for-verification]

Bolt signs the payload with HMAC SHA-256, using the **signing secret** as the key, and Base64 encodes the result so it travels as plain text. Compute the same value and compare it to the header:

<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.
