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

# Verify Signatures

> Validate that incoming webhooks are sent by DroneBundle.

Every webhook delivery is signed with your webhook's secret using HMAC-SHA256. Always verify the signature before processing the payload to ensure the request is authentic.

## How it works

1. DroneBundle computes an HMAC-SHA256 hash of the raw JSON request body using your webhook's secret
2. The hash is sent in the `x-dronebundle-signature` header in the format `sha256=<hex_digest>`
3. Your server computes the same hash and compares the two values

<Warning>
  Always use the raw request body for verification, not a parsed-and-re-serialized version. Re-serializing JSON can change key order or whitespace, which will produce a different hash.
</Warning>

## Code examples

<CodeGroup>
  ```javascript Node.js theme={null}
  import { createHmac, timingSafeEqual } from 'node:crypto';

  function verifyWebhookSignature(body, secret, signatureHeader) {
    const expected = createHmac('sha256', secret)
      .update(body)
      .digest('hex');

    const received = signatureHeader.replace('sha256=', '');

    return timingSafeEqual(
      Buffer.from(expected, 'hex'),
      Buffer.from(received, 'hex')
    );
  }
  ```

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

  def verify_webhook_signature(body: bytes, secret: str, signature_header: str) -> bool:
      expected = hmac.new(
          key=secret.encode(),
          msg=body,
          digestmod=hashlib.sha256
      ).hexdigest()

      received = signature_header.replace("sha256=", "")

      return hmac.compare_digest(expected, received)
  ```

  ```java Java theme={null}
  import javax.crypto.Mac;
  import javax.crypto.spec.SecretKeySpec;

  public static boolean verifyWebhookSignature(String body, String secret, String signatureHeader) throws Exception {
      Mac mac = Mac.getInstance("HmacSHA256");
      mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
      byte[] hash = mac.doFinal(body.getBytes("UTF-8"));
      StringBuilder expected = new StringBuilder();
      for (byte b : hash) {
          expected.append(String.format("%02x", b));
      }
      String received = signatureHeader.replace("sha256=", "");
      return expected.toString().equals(received);
  }
  ```

  ```csharp C# theme={null}
  using System.Security.Cryptography;
  using System.Text;

  public static bool VerifyWebhookSignature(string body, string secret, string signatureHeader)
  {
      using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
      byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(body));
      string expected = BitConverter.ToString(hash).Replace("-", "").ToLower();
      string received = signatureHeader.Replace("sha256=", "");
      return expected == received;
  }
  ```

  ```php PHP theme={null}
  function verifyWebhookSignature(string $body, string $secret, string $signatureHeader): bool {
      $expected = hash_hmac('sha256', $body, $secret);
      $received = str_replace('sha256=', '', $signatureHeader);
      return hash_equals($expected, $received);
  }
  ```

  ```go Go theme={null}
  import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "strings"
  )

  func verifyWebhookSignature(body []byte, secret string, signatureHeader string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expected := hex.EncodeToString(mac.Sum(nil))
    received := strings.TrimPrefix(signatureHeader, "sha256=")
    return hmac.Equal([]byte(expected), []byte(received))
  }
  ```

  ```rust Rust theme={null}
  use hmac::{Hmac, Mac};
  use sha2::Sha256;

  type HmacSha256 = Hmac<Sha256>;

  fn verify_webhook_signature(body: &[u8], secret: &str, signature_header: &str) -> bool {
      let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
          .expect("HMAC can take key of any size");
      mac.update(body);
      let expected = hex::encode(mac.finalize().into_bytes());
      let received = signature_header
          .strip_prefix("sha256=")
          .unwrap_or(signature_header);
      expected == received
  }
  ```
</CodeGroup>
