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

# Validate signature

> Validate the signature of webhooks that you receive.

To ensure the authenticity of a webhook, we sign each request with your **webhook secret** and places the signature in the `X-Webhook-Signature` header. This enables your server to confirm that the payload has not been altered and originates from a trusted source.

<Card title="Create a Webhook endpoint" icon="webhook" href="https://dash.bitgpt.xyz/developers/webhooks" arrow="true">
  The webhook secret is unique to each organization-webhook endpoint pair. You get a webhook secret after creating a webhook endpoint.
</Card>

## Signature validation snippets

<CodeGroup dropdown>
  ```php Webhook.php wrap lines  theme={"system"}
  /**
   * @param array<string, string> $headers  // Incoming HTTP headers
   * @param string                $rawBody  // Raw JSON request body
   * @param string                $secret   // Shared secret key
   * @return bool                           // Whether the signature is valid
   */
  function validate_webhook(array $headers, string $rawBody, string $secret): bool {
    $receivedSignature = $headers['X-Webhook-Signature'] ?? '';
    $timestamp = $headers['X-Webhook-Timestamp'] ?? '';

    $body = json_decode($rawBody, true);

    $webhookPayload = [
      "webhook_id" => $body["webhook_id"],
      "url" => $body["url"],
      "event" => $body["event"],
      "resource_id" => $body["resource_id"],
      "payload" => $body["payload"],
      "timestamp" => $timestamp
    ];

    $expectedSignature = hash_hmac(
      'sha256',
      json_encode($webhookPayload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
      $secret
    );

    return hash_equals($expectedSignature, $receivedSignature);
  }
  ```

  ```javascript Webhook.js wrap lines  theme={"system"}
  /**

  @param {import("express").Request} req - Express request object

  @param {string} secret - Shared secret key

  @returns {boolean} Whether the webhook signature is valid
  */

  const crypto = require("crypto");

  function validateWebhook(req, secret) {
    const signature = req.header("X-Webhook-Signature");
    const timestamp = req.header("X-Webhook-Timestamp");

    const body = req.body;

    const payload = {
      webhook_id: body.webhook_id,
      url: body.url,
      event: body.event,
      resource_id: body.resource_id,
      payload: body.payload,
      timestamp: timestamp
    };

    const json = JSON.stringify(payload);
    const expectedSignature = crypto
      .createHmac("sha256", secret)
      .update(json)
      .digest("hex");

    return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature));
  }
  ```

  ```typescript Webhook.ts wrap lines  theme={"system"}
  import crypto from "crypto";

  /**
   * Webhook payload structure as expected from the sender
   */
  interface WebhookPayload {
    webhook_id: string;
    url: string;
    event: string;
    resource_id: string;
    payload: unknown;
    timestamp: string;
  }

  /**
   * Headers relevant to the webhook signature
   */
  interface WebhookHeaders {
    'x-webhook-signature'?: string;
    'x-webhook-timestamp'?: string;
  }

  /**
   * Validates a webhook signature using HMAC SHA256
   *
   * @param body    - The full parsed JSON request body
   * @param headers - HTTP headers (lowercased keys)
   * @param secret  - Shared secret for HMAC
   * @returns       - true if the signature is valid, false otherwise
   */
  function validateWebhook(
    body: any,
    headers: WebhookHeaders,
    secret: string
  ): boolean {
    const signature = headers["x-webhook-signature"];
    const timestamp = headers["x-webhook-timestamp"];

    if (!signature || !timestamp) return false;

    const payload: WebhookPayload = {
      webhook_id: body.webhook_id,
      url: body.url,
      event: body.event,
      resource_id: body.resource_id,
      payload: body.payload,
      timestamp: timestamp,
    };

    const json = JSON.stringify(payload);

    const expectedSignature = crypto
      .createHmac("sha256", secret)
      .update(json)
      .digest("hex");

    return crypto.timingSafeEqual(
      Buffer.from(signature, "utf-8"),
      Buffer.from(expectedSignature, "utf-8")
    );
  }
  ```

  ```python Webhook.py wrap lines  theme={"system"}
  import hmac
  import hashlib
  import json
  from typing import Any
  from flask import Request

  def validate_webhook(request: Request, secret: str) -> bool:
    """
    Validates the webhook signature using HMAC SHA256.

    Args:
      request (flask.Request): The incoming HTTP request.
      secret (str): The shared secret.

    Returns:
      bool: True if the signature is valid, False otherwise.
    """
    received_signature: str = request.headers.get("X-Webhook-Signature", "")
    timestamp: str = request.headers.get("X-Webhook-Timestamp", "")

    body: dict[str, Any] = request.get_json()

    webhook_payload = {
      "webhook_id": body["webhook_id"],
      "url": body["url"],
      "event": body["event"],
      "resource_id": body["resource_id"],
      "payload": body["payload"],
      "timestamp": timestamp
    }

    json_data: str = json.dumps(webhook_payload, ensure_ascii=False, separators=(',', ':'))
    expected_signature: str = hmac.new(
      secret.encode(), json_data.encode(), hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(expected_signature, received_signature)

  ```

  ```java Webhook.java wrap lines  theme={"system"}
  import javax.crypto.Mac;
  import javax.crypto.spec.SecretKeySpec;
  import java.nio.charset.StandardCharsets;
  import java.util.Map;
  import com.fasterxml.jackson.databind.ObjectMapper;

  public class WebhookValidator {
    public static boolean validate(
      Map<String, Object> body,
      Map<String, String> headers,
      String secret
    ) throws Exception {
      String signature = headers.get("X-Webhook-Signature");
      String timestamp = headers.get("X-Webhook-Timestamp");

      Map<String, Object> payload = Map.of(
        "webhook_id", body.get("webhook_id"),
        "url", body.get("url"),
        "event", body.get("event"),
        "resource_id", body.get("resource_id"),
        "payload", body.get("payload"),
        "timestamp", timestamp
      );

      ObjectMapper mapper = new ObjectMapper();
      String json = mapper.writeValueAsString(payload);

      Mac hmac = Mac.getInstance("HmacSHA256");
      hmac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
      byte[] hash = hmac.doFinal(json.getBytes(StandardCharsets.UTF_8));

      StringBuilder hex = new StringBuilder();
      for (byte b : hash) {
        hex.append(String.format("%02x", b));
      }

      return hex.toString().equals(signature);
    }
  }
  ```

  ```go Webhook.go wrap lines  theme={"system"}
  package main

  import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "net/http"
    "io"
  )

  func validateWebhook(r *http.Request, secret string) bool {
    signature := r.Header.Get("X-Webhook-Signature")
    timestamp := r.Header.Get("X-Webhook-Timestamp")

    var body map[string]interface{}
    raw, _ := io.ReadAll(r.Body)
    json.Unmarshal(raw, &body)

    payload := map[string]interface{}{
      "webhook_id":  body["webhook_id"],
      "url":         body["url"],
      "event":       body["event"],
      "resource_id": body["resource_id"],
      "payload":     body["payload"],
      "timestamp":   timestamp,
    }

    payloadBytes, _ := json.Marshal(payload)

    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(payloadBytes)
    expected := hex.EncodeToString(mac.Sum(nil))

    return hmac.Equal([]byte(expected), []byte(signature))
  }
  ```

  ```rust Webhook.rs wrap lines  theme={"system"}
  use std::collections::HashMap;
  use hmac::{Hmac, Mac};
  use sha2::Sha256;
  use serde_json::json;

  type HmacSha256 = Hmac<Sha256>;

  pub fn validate_webhook(
    body: &HashMap<String, serde_json::Value>,
    headers: &HashMap<String, String>,
    secret: &str,
  ) -> bool {
    let signature = headers.get("x-webhook-signature").unwrap_or(&"".to_string());
    let timestamp = headers.get("x-webhook-timestamp").unwrap_or(&"".to_string());

    let payload = json!({
      "webhook_id": body.get("webhook_id"),
      "url": body.get("url"),
      "event": body.get("event"),
      "resource_id": body.get("resource_id"),
      "payload": body.get("payload"),
      "timestamp": timestamp
    });

    let payload_str = payload.to_string();

    let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).expect("HMAC init failed");
    mac.update(payload_str.as_bytes());

    let expected_signature = hex::encode(mac.finalize().into_bytes());

    expected_signature == *signature
  }
  ```
</CodeGroup>
