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

# How-to: Validating Webhook Events

> Hireflix can optionally sign webhook events to prove authenticity.

Each event request includes a `x-hireflix-signature` header. This header can be used to verify the authenticity of the webhook event.

You can verify this signature using your webhook’s secret key, available under Profile → Webhooks → "Reveal Key" in the Hireflix Dashboard.

<img src="https://mintcdn.com/hireflixsl/MGapCi589uqwBDzA/images/revealwebhookkey.png?fit=max&auto=format&n=MGapCi589uqwBDzA&q=85&s=cce711781ce132f5066073f731342cb8" alt="Webhook secret key (UI Hireflix)" width="1054" height="639" data-path="images/revealwebhookkey.png" />

## Verification Steps 🔑

* **Extract the signature** from the `x-hireflix-signature` header. It’s Base64-encoded, so decode it first.
* **Compute your own signature**
  * Use **HMAC with SHA-256**.
  * Use your webhook’s secret key as the key.
  * Use the raw JSON payload (request body) as the message.
* **Compare the signatures**\
  If your computed signature matches the header value, the event is authentic.

Here's an example if you are directly intercepting the request:

<CodeGroup>
  ```javascript JavaScript theme={null}
  import crypto from "crypto";

  function verifyHireflixWebhook(req, secret) {
    const signature = Buffer.from(req.headers["x-hireflix-signature"], "base64");
    const expected = crypto
      .createHmac("sha256", secret)
      .update(JSON.stringify(req.body))
      .digest();

    return crypto.timingSafeEqual(signature, expected);
  }
  ```

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

  def verify_hireflix_webhook(request, secret):
      signature_b64 = request.headers.get("x-hireflix-signature")
      signature = base64.b64decode(signature_b64)

      expected = hmac.new(
          secret.encode("utf-8"),
          json.dumps(request.json).encode("utf-8"),
          hashlib.sha256
      ).digest()

      # Use hmac.compare_digest to prevent timing attacks
      return hmac.compare_digest(signature, expected)
  ```

  ```go Go theme={null}
  package main

  import (
  	"crypto/hmac"
  	"crypto/sha256"
  	"crypto/subtle"
  	"encoding/base64"
  	"encoding/json"
  	"net/http"
  )

  func VerifyHireflixWebhook(r *http.Request, secret string) bool {
  	signatureB64 := r.Header.Get("x-hireflix-signature")
  	signature, err := base64.StdEncoding.DecodeString(signatureB64)
  	if err != nil {
  		return false
  	}

  	var body interface{}
  	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
  		return false
  	}

  	bodyBytes, _ := json.Marshal(body)
  	mac := hmac.New(sha256.New, []byte(secret))
  	mac.Write(bodyBytes)
  	expected := mac.Sum(nil)

  	return subtle.ConstantTimeCompare(signature, expected) == 1
  }
  ```

  ```java Java theme={null}
  import javax.crypto.Mac;
  import javax.crypto.spec.SecretKeySpec;
  import java.util.Base64;
  import java.nio.charset.StandardCharsets;
  import java.security.MessageDigest;

  public class HireflixVerifier {

      public static boolean verifyHireflixWebhook(String body, String signatureHeader, String secret) {
          try {
              byte[] signature = Base64.getDecoder().decode(signatureHeader);

              Mac mac = Mac.getInstance("HmacSHA256");
              mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
              byte[] expected = mac.doFinal(body.getBytes(StandardCharsets.UTF_8));

              return MessageDigest.isEqual(signature, expected); // timing-safe compare
          } catch (Exception e) {
              return false;
          }
      }
  }
  ```

  ```php PHP theme={null}
  <?php

  function verifyHireflixWebhook($requestBody, $headers, $secret) {
      $signatureB64 = $headers['x-hireflix-signature'] ?? '';
      $signature = base64_decode($signatureB64, true);

      $expected = hash_hmac('sha256', json_encode($requestBody), $secret, true);

      // Use hash_equals for timing-safe comparison
      return hash_equals($signature, $expected);
  }
  ```
</CodeGroup>

Here's an example that shows the full code in JavaScript, and prints `true` to your console if the signature matches the expected one.

```javascript theme={null}
import crypto from "crypto";

function verifyHireflixWebhook(secret) {
  // Signature extracted from the request headers (x-hireflix-signature)
  const signature = Buffer.from("0iHqlyysUmN4UlZdU2zY+QUZHD1URa2MJsYn3p84zUY=", "base64");
  const expected = crypto
    .createHmac("sha256", secret)
    .update(JSON.stringify({
      "event": "interview.create",
      "data": {
        "id": "68ff5ed8b07cc148bddcae02",
        "position": {
          "id": "68b43e38b19cf131a17c543f",
          "name": "Social Media Manager"
        },
        "externalId": null,
        "status": "pending",
        "hash": "25eXrLpj",
        "createdAt": 1761566424331,
        "candidate": {
          "name": "Stef Jordens",
          "firstName": "Stef",
          "lastName": "Jordens",
          "email": "stef@nespf.com",
          "phone": null
        },
        "score": null,
        "completed": null,
        "deleted": null,
        "archived": null,
        "finalist": null,
        "answered": false,
        "thumbnail": null,
        "url": {
          "short": "https://hflx.io/c/25eXrLpj",
          "private": "https://admin.hireflix.com/jobs/68b43e38b19cf131a17c543f/interview/68ff5ed8b07cc148bddcae02",
          "public": "https://app.hireflix.com/25eXrLpj"
        }
      },
      "date": 1761566424499
    }))
    .digest();

  return crypto.timingSafeEqual(signature, expected);
}

// webhook secret from Hireflix dashboard
const mysecret = "lbH4lVZYz77vubJX3pWRrdRY3AcMKdTjaoznzxsSygM="

// Does it match?
const match = verifyHireflixWebhook(mysecret);
console.log(match)
```
