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

# Verifying HMAC Signature

> Before processing any request from Genuka, verify the HMAC signature to ensure authenticity and security.

## Why HMAC verification matters

Before performing **any operation** (such as exchanging an authorization code for an access token), your application must verify that the request **really comes from Genuka**.

When Genuka sends the installation callback to your application, the request includes the following parameters:

```
company_id, code, timestamp, hmac, redirect_to
```

The `hmac` parameter is a **cryptographic signature** generated by Genuka. Your application must **recompute this signature** using your `GENUKA_CLIENT_SECRET` and compare it to the one received.

If they **don’t match**, you **must reject the request** — otherwise, your app could be impersonated.

> ⚠️ **Important:**
> If HMAC verification is not implemented or fails, your application will **never be validated or published** in the Genuka App Store.

***

## Example: Validating HMAC

Below are robust examples to validate HMAC in different programming languages. Each example recreates the signed string exactly, computes the HMAC using your client secret and compares both values using a constant-time comparison.

<CodeGroup>
  ```javascript Node.js theme={null}
  import crypto from "crypto";

  const MAX_AGE_SECONDS = 300; // 5 minutes

  export function validateHmac({
    hmac: receivedHmac,
    timestamp,
    companyId,
  }) {
    if (!receivedHmac || !timestamp || !companyId) return false;

    // 1. Check timestamp to prevent replay attacks
    const now = Math.floor(Date.now() / 1000);
    if (Math.abs(now - parseInt(timestamp)) > MAX_AGE_SECONDS) {
      return false;
    }

    // 2. Recreate the exact string used to generate the HMAC on Genuka's side
    const stringToHash = `company_id=${companyId}&timestamp=${timestamp}`;

    // 3. Compute the HMAC using your GENUKA_CLIENT_SECRET
    const secret = process.env.GENUKA_CLIENT_SECRET || "";
    const computedHmac = crypto
      .createHmac("sha256", secret)
      .update(stringToHash)
      .digest("hex");

    // 4. Compare in constant time to avoid timing attacks
    try {
      return crypto.timingSafeEqual(
        Buffer.from(computedHmac, "hex"),
        Buffer.from(receivedHmac, "hex")
      );
    } catch (err) {
      return false;
    }
  }
  ```

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

  define('MAX_AGE_SECONDS', 300); // 5 minutes

  function validateHmac(
      string $receivedHmac,
      string $timestamp,
      string $companyId
  ): bool {
      if (empty($receivedHmac) || empty($timestamp) || empty($companyId)) {
          return false;
      }

      // 1. Check timestamp to prevent replay attacks
      $now = time();
      if (abs($now - intval($timestamp)) > MAX_AGE_SECONDS) {
          return false;
      }

      // 2. Recreate the exact string used to generate the HMAC on Genuka's side
      $stringToHash = "company_id={$companyId}&timestamp={$timestamp}";

      // 3. Compute the HMAC using your GENUKA_CLIENT_SECRET
      $secret = $_ENV['GENUKA_CLIENT_SECRET'] ?? '';
      $computedHmac = hash_hmac('sha256', $stringToHash, $secret);

      // 4. Compare in constant time to avoid timing attacks
      return hash_equals($computedHmac, $receivedHmac);
  }

  // Usage example in Laravel or plain PHP
  $isValid = validateHmac(
      $_GET['hmac'] ?? '',
      $_GET['timestamp'] ?? '',
      $_GET['company_id'] ?? ''
  );

  if (!$isValid) {
      http_response_code(401);
      echo json_encode(['error' => 'Invalid HMAC. Request not from Genuka.']);
      exit;
  }
  ```

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

  MAX_AGE_SECONDS = 300  # 5 minutes

  def validate_hmac(received_hmac: str, timestamp: str, company_id: str) -> bool:
      if not received_hmac or not timestamp or not company_id:
          return False

      # 1. Check timestamp to prevent replay attacks
      now = int(time.time())
      if abs(now - int(timestamp)) > MAX_AGE_SECONDS:
          return False

      # 2. Recreate the exact string used to generate the HMAC on Genuka's side
      string_to_hash = f"company_id={company_id}&timestamp={timestamp}"

      # 3. Compute the HMAC using your GENUKA_CLIENT_SECRET
      secret = os.environ.get('GENUKA_CLIENT_SECRET', '')
      computed_hmac = hmac.new(
          secret.encode('utf-8'),
          string_to_hash.encode('utf-8'),
          hashlib.sha256
      ).hexdigest()

      # 4. Compare in constant time to avoid timing attacks
      return hmac.compare_digest(computed_hmac, received_hmac)


  # Usage example with Flask
  from flask import Flask, request, jsonify

  app = Flask(__name__)

  @app.route('/callback')
  def callback():
      is_valid = validate_hmac(
          request.args.get('hmac', ''),
          request.args.get('timestamp', ''),
          request.args.get('company_id', '')
      )

      if not is_valid:
          return jsonify({'error': 'Invalid HMAC. Request not from Genuka.'}), 401

      # HMAC valid — proceed with token exchange
      return jsonify({'message': 'HMAC validated'}), 200
  ```
</CodeGroup>

<Note>
  All examples include **replay attack prevention** by validating the timestamp (requests older than 5 minutes are rejected).
</Note>

***

## Example verification flow

<Steps>
  <Step title="Receive callback from Genuka">
    Genuka sends an installation callback to your app with the following parameters:

    ```
    /callback?company_id=123&timestamp=1731456700&hmac=abcd1234&code=xyz
    ```
  </Step>

  <Step title="Validate HMAC before processing">
    Extract parameters and validate the HMAC **before any other processing**:

    <CodeGroup>
      ```javascript Node.js theme={null}
      // Express / Next.js example
      app.get('/callback', async (req, res) => {
        const { company_id, timestamp, hmac, code } = req.query;

        const isValid = validateHmac({
          hmac: String(hmac || ''),
          timestamp: String(timestamp || ''),
          companyId: String(company_id || ''),
        });

        if (!isValid) {
          return res.status(401).json({ error: 'Invalid HMAC. Request not from Genuka.' });
        }

        // HMAC valid — proceed to exchange the code for an access token
        res.status(200).send('HMAC validated — proceeding with token exchange');
      });
      ```

      ```php PHP theme={null}
      // Laravel example
      Route::get('/callback', function (Request $request) {
          $isValid = validateHmac(
              $request->query('hmac', ''),
              $request->query('timestamp', ''),
              $request->query('company_id', '')
          );

          if (!$isValid) {
              return response()->json(['error' => 'Invalid HMAC. Request not from Genuka.'], 401);
          }

          // HMAC valid — proceed to exchange the code for an access token
          return response()->json(['message' => 'HMAC validated']);
      });
      ```

      ```python Python theme={null}
      # Django example
      from django.http import JsonResponse

      def callback(request):
          is_valid = validate_hmac(
              request.GET.get('hmac', ''),
              request.GET.get('timestamp', ''),
              request.GET.get('company_id', '')
          )

          if not is_valid:
              return JsonResponse({'error': 'Invalid HMAC. Request not from Genuka.'}, status=401)

          # HMAC valid — proceed to exchange the code for an access token
          return JsonResponse({'message': 'HMAC validated'})
      ```
    </CodeGroup>
  </Step>

  <Step title="Exchange code for access token">
    Once HMAC is validated, proceed to exchange the `code` for an access token. See the [Authentication guide](/getting-started/authentication) for details.
  </Step>
</Steps>

***

## Best Practices

* ✅ **Always** verify the HMAC **before any other processing** (e.g., before exchanging the code for a token).
* ✅ **Never** log or expose your `GENUKA_CLIENT_SECRET`.
* ✅ Use constant-time comparison (e.g., `crypto.timingSafeEqual`) to avoid timing attacks.
* ✅ Immediately reject any request with an invalid HMAC and return an appropriate HTTP status (401 or 403).
* ✅ Optionally, validate the `timestamp` to prevent replay attacks (for example, reject requests older than 5 minutes).
* ✅ Record secure logs for failed verifications (without logging secrets or HMAC values) to help debugging.

***

## Summary

| Step | Description                                                               |
| ---- | ------------------------------------------------------------------------- |
| 1    | Receive callback parameters (`company_id`, `timestamp`, `hmac`, `code`)   |
| 2    | Recreate `stringToHash = "company_id={company_id}&timestamp={timestamp}"` |
| 3    | Generate HMAC using `GENUKA_CLIENT_SECRET`                                |
| 4    | Compare with received `hmac` using constant-time comparison               |
| 5    | Reject if invalid, proceed if valid                                       |

> 🔒 Implementing HMAC verification is mandatory to protect your integration and validate your app for the Genuka App Store.
