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

# Verification des signatures webhook

> Authentifiez les webhooks entrants a l'aide de signatures HMAC-SHA256

Chaque requete webhook inclut une signature cryptographique pour que vous puissiez verifier qu'elle provient bien d'Allo. Sans verification, un attaquant pourrait envoyer de faux evenements a votre endpoint.

## En-tetes de signature

Chaque requete POST webhook inclut trois en-tetes :

| En-tete             | Description                                      | Exemple                                  |
| ------------------- | ------------------------------------------------ | ---------------------------------------- |
| `webhook-id`        | Identifiant unique du message                    | `msg_2NfDKEm9sF8xK3pQr1Zt`               |
| `webhook-timestamp` | Horodatage Unix (secondes) de l'envoi du message | `1710510600`                             |
| `webhook-signature` | Une ou plusieurs signatures HMAC-SHA256          | `v1,K5oZfzN95Z3M+nrYOgGHfLGC7t8VjLtV...` |

## Secret de signature

Lorsque vous creez un endpoint webhook, la reponse inclut un champ `signing_secret`. Ce secret a le format `whsec_<base64key>` et est utilise pour verifier les signatures.

Stockez-le de maniere securisee -- traitez-le comme un mot de passe. S'il est compromis, effectuez une rotation via l'API.

## Algorithme de verification

<Steps>
  <Step title="Extraire les en-tetes">
    Recuperez `webhook-id`, `webhook-timestamp` et `webhook-signature` depuis les en-tetes de la requete.
  </Step>

  <Step title="Valider l'horodatage">
    Rejetez les requetes dont l'horodatage est eloigne de plus de 5 minutes de l'heure actuelle de votre serveur. Cela empeche les attaques par rejeu.
  </Step>

  <Step title="Construire le contenu signe">
    Concatenez l'identifiant du webhook, l'horodatage et le corps brut de la requete avec des points :

    ```
    {webhook-id}.{webhook-timestamp}.{raw_body}
    ```
  </Step>

  <Step title="Calculer la signature attendue">
    1. Supprimez le prefixe `whsec_` de votre secret de signature.
    2. Decodez en base64 la chaine restante pour obtenir les octets de la cle.
    3. Calculez le HMAC-SHA256 du contenu signe avec les octets de la cle.
    4. Encodez le resultat en base64.
  </Step>

  <Step title="Comparer les signatures">
    L'en-tete `webhook-signature` peut contenir plusieurs signatures separees par des espaces (pour la rotation des secrets). Chacune a un prefixe `v1,`. Comparez votre signature calculee avec chacune d'entre elles. Si l'une correspond, la signature est valide.
  </Step>
</Steps>

<Warning>
  Utilisez toujours le **corps brut de la requete** pour la verification. Si votre framework parse le JSON et le re-serialise, la signature ne correspondra pas.
</Warning>

## Exemples de code

<Tabs>
  <Tab title="Node.js">
    ```javascript theme={null}
    const crypto = require("crypto");

    function verifyWebhook(payload, headers, secret) {
      const webhookId = headers["webhook-id"];
      const webhookTimestamp = headers["webhook-timestamp"];
      const webhookSignature = headers["webhook-signature"];

      // Validate timestamp (5-minute tolerance)
      const now = Math.floor(Date.now() / 1000);
      if (Math.abs(now - parseInt(webhookTimestamp)) > 300) {
        throw new Error("Timestamp too old");
      }

      // Build signed content
      const signedContent = `${webhookId}.${webhookTimestamp}.${payload}`;

      // Decode secret (strip "whsec_" prefix, base64-decode)
      const secretBytes = Buffer.from(secret.split("_")[1], "base64");

      // Compute HMAC-SHA256
      const computed = crypto
        .createHmac("sha256", secretBytes)
        .update(signedContent)
        .digest("base64");

      // Compare against each signature in the header
      const signatures = webhookSignature
        .split(" ")
        .map((sig) => sig.split(",")[1]);

      if (!signatures.some((sig) => sig === computed)) {
        throw new Error("Invalid signature");
      }

      return JSON.parse(payload);
    }

    // Express.js example
    const express = require("express");
    const app = express();

    app.post(
      "/webhooks/allo",
      express.raw({ type: "application/json" }),
      (req, res) => {
        try {
          const event = verifyWebhook(
            req.body.toString(),
            req.headers,
            process.env.WEBHOOK_SECRET
          );
          console.log("Verified event:", event.topic);
          res.sendStatus(200);
        } catch (err) {
          console.error("Verification failed:", err.message);
          res.sendStatus(400);
        }
      }
    );
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import hmac
    import hashlib
    import base64
    import time
    import json

    def verify_webhook(payload: bytes, headers: dict, secret: str) -> dict:
        webhook_id = headers.get("webhook-id")
        webhook_timestamp = headers.get("webhook-timestamp")
        webhook_signature = headers.get("webhook-signature")

        # Validate timestamp (5-minute tolerance)
        now = int(time.time())
        if abs(now - int(webhook_timestamp)) > 300:
            raise ValueError("Timestamp too old")

        # Build signed content
        signed_content = f"{webhook_id}.{webhook_timestamp}.{payload.decode()}"

        # Decode secret (strip "whsec_" prefix, base64-decode)
        secret_bytes = base64.b64decode(secret.split("_")[1])

        # Compute HMAC-SHA256
        computed = base64.b64encode(
            hmac.new(
                secret_bytes,
                signed_content.encode(),
                hashlib.sha256,
            ).digest()
        ).decode()

        # Compare against each signature in the header
        signatures = [
            sig.split(",")[1]
            for sig in webhook_signature.split(" ")
        ]

        if computed not in signatures:
            raise ValueError("Invalid signature")

        return json.loads(payload)

    # Flask example
    from flask import Flask, request

    app = Flask(__name__)

    @app.route("/webhooks/allo", methods=["POST"])
    def handle_webhook():
        try:
            event = verify_webhook(
                request.get_data(),
                request.headers,
                os.environ["WEBHOOK_SECRET"],
            )
            print(f"Verified event: {event['topic']}")
            return "", 200
        except ValueError as e:
            print(f"Verification failed: {e}")
            return "", 400
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    package main

    import (
        "crypto/hmac"
        "crypto/sha256"
        "encoding/base64"
        "fmt"
        "math"
        "net/http"
        "strconv"
        "strings"
        "time"
    )

    func verifyWebhook(payload []byte, headers http.Header, secret string) error {
        webhookID := headers.Get("webhook-id")
        webhookTimestamp := headers.Get("webhook-timestamp")
        webhookSignature := headers.Get("webhook-signature")

        // Validate timestamp (5-minute tolerance)
        ts, _ := strconv.ParseInt(webhookTimestamp, 10, 64)
        if math.Abs(float64(time.Now().Unix()-ts)) > 300 {
            return fmt.Errorf("timestamp too old")
        }

        // Build signed content
        signedContent := fmt.Sprintf("%s.%s.%s", webhookID, webhookTimestamp, string(payload))

        // Decode secret (strip "whsec_" prefix, base64-decode)
        parts := strings.SplitN(secret, "_", 2)
        secretBytes, _ := base64.StdEncoding.DecodeString(parts[1])

        // Compute HMAC-SHA256
        mac := hmac.New(sha256.New, secretBytes)
        mac.Write([]byte(signedContent))
        computed := base64.StdEncoding.EncodeToString(mac.Sum(nil))

        // Compare against each signature in the header
        for _, sig := range strings.Split(webhookSignature, " ") {
            parts := strings.SplitN(sig, ",", 2)
            if len(parts) == 2 && parts[1] == computed {
                return nil
            }
        }

        return fmt.Errorf("invalid signature")
    }
    ```
  </Tab>
</Tabs>

<Warning>
  Verifiez toujours les signatures en production. Ignorer la verification expose votre application a des evenements webhook usurpes.
</Warning>
