Aller au contenu principal
Guides techniques

Sécuriser vos webhooks Rodz : vérification HMAC-SHA256

Peter Cools · · 13 min de lecture

En résumé : Tout endpoint exposé sur Internet peut recevoir des requêtes de n’importe qui. Sans vérification, un attaquant peut envoyer de faux signaux à votre serveur et corrompre vos données. La vérification HMAC-SHA256 résout ce problème : Rodz signe chaque livraison de webhook avec un secret partagé, et votre serveur vérifie la signature avant de traiter la requête. Ce guide vous montre comment implémenter cette vérification en Node.js et en Python, étape par étape.

Pourquoi la sécurité des webhooks est essentielle

Quand vous configurez un webhook, vous exposez un endpoint HTTP sur Internet. Votre serveur accepte les requêtes POST entrantes et traite les données qu’elles contiennent. Le problème, c’est que n’importe qui peut envoyer une requête POST à cette URL. Un acteur malveillant pourrait forger de faux signaux d’affaires, injecter des données corrompues dans votre CRM, déclencher des workflows automatisés sur de fausses informations, ou encore saturer votre endpoint avec des requêtes frauduleuses.

Sans mécanisme de vérification, votre serveur n’a aucun moyen de distinguer une requête légitime envoyée par Rodz d’une requête fabriquée par un tiers. C’est exactement le rôle de la vérification HMAC-SHA256 : prouver cryptographiquement que la requête provient bien de Rodz et que son contenu n’a pas été altéré en transit.

Si vous n’avez pas encore mis en place vos webhooks, commencez par lire le guide de configuration des webhooks Rodz. Et pour une vue d’ensemble de l’API, consultez la référence complète des endpoints.

Prérequis

Avant de commencer, assurez-vous d’avoir les éléments suivants :

  1. Un webhook Rodz fonctionnel. Votre endpoint doit déjà recevoir des signaux. Si ce n’est pas le cas, suivez d’abord le guide de configuration des webhooks.
  2. Une clé API Rodz active. Vous en aurez besoin pour enregistrer votre webhook avec un secret.
  3. Node.js 18+ ou Python 3.8+ installé sur votre machine (selon le langage que vous utilisez).
  4. Un outil en ligne de commande comme cURL ou openssl pour tester la génération de signatures manuellement.
  5. Des notions de base en cryptographie. Vous n’avez pas besoin d’être expert, mais comprendre ce qu’est un hash et une clé secrète vous aidera à suivre le guide.

Comment fonctionne la vérification HMAC-SHA256

Le principe en trois points

HMAC-SHA256 est un code d’authentification de message basé sur un hash (Hash-based Message Authentication Code). Le mécanisme repose sur trois éléments :

  • Un secret partagé. Lors de l’enregistrement de votre webhook, vous fournissez un secret (ou Rodz en génère un pour vous). Ce secret n’est connu que de vous et de Rodz.
  • Une signature dans le header. À chaque livraison de webhook, Rodz calcule le HMAC-SHA256 du corps brut de la requête en utilisant votre secret, puis inclut le résultat dans le header X-Rodz-Signature.
  • Une vérification côté serveur. Votre serveur recalcule le HMAC-SHA256 du corps brut reçu avec le même secret, puis compare le résultat avec la valeur du header. Si les deux correspondent, la requête est authentique.

Pourquoi HMAC-SHA256 et pas un simple hash ?

Un hash SHA256 sans clé secrète ne prouve rien. N’importe qui peut calculer le SHA256 d’un corps de requête. Le HMAC ajoute la notion de clé secrète : sans connaître le secret, il est impossible de produire une signature valide. C’est ce qui garantit l’authenticité du message.

Étape 1 : Générer un secret lors de l’enregistrement du webhook

Quand vous enregistrez votre webhook via l’API Rodz, incluez le paramètre secret dans votre requête. Ce secret doit être une chaîne aléatoire d’au moins 32 caractères.

Vous pouvez générer un secret avec openssl :

openssl rand -hex 32

Cette commande produit une chaîne hexadécimale de 64 caractères (32 octets). Copiez ce résultat et utilisez-le dans votre requête d’enregistrement :

curl -X POST https://api.rodz.io/v1/webhooks \
  -H "Authorization: Bearer VOTRE_CLE_API" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://votre-serveur.com/webhooks/rodz",
    "events": ["signal.detected"],
    "secret": "a1b2c3d4e5f6...votre_secret_genere_ici"
  }'

Rodz renvoie une confirmation avec l’identifiant du webhook :

{
  "id": "wh_abc123def456",
  "url": "https://votre-serveur.com/webhooks/rodz",
  "events": ["signal.detected"],
  "active": true,
  "created_at": "2026-03-13T10:00:00Z"
}

Stockez votre secret dans une variable d’environnement sur votre serveur. Ne le codez jamais en dur dans votre code source.

export RODZ_WEBHOOK_SECRET="a1b2c3d4e5f6...votre_secret_genere_ici"

Étape 2 : Comprendre le header X-Rodz-Signature

À chaque livraison de webhook, Rodz ajoute un header X-Rodz-Signature à la requête POST. Ce header contient la signature HMAC-SHA256 du corps brut de la requête, préfixée par sha256= :

X-Rodz-Signature: sha256=5d41402abc4b2a76b9719d911017c592ae3a5f4c3b2e1d0f9e8d7c6b5a4f3e2d

Le format sha256= vous permet de vérifier l’algorithme utilisé et facilite l’évolution future vers d’autres algorithmes si nécessaire.

Votre serveur doit extraire ce header, retirer le préfixe sha256=, puis comparer la valeur restante avec le HMAC que vous calculez de votre côté.

Étape 3 : Implémenter la vérification en Node.js

Voici une implémentation complète avec Express.js. Le point crucial est de capturer le corps brut de la requête (raw body), pas la version parsée en JSON.

const express = require('express');
const crypto = require('crypto');

const app = express();
const WEBHOOK_SECRET = process.env.RODZ_WEBHOOK_SECRET;

// Capturer le corps brut AVANT le parsing JSON
app.use('/webhooks/rodz', express.raw({ type: 'application/json' }));

function verifySignature(rawBody, signatureHeader) {
  if (!signatureHeader) {
    return false;
  }

  // Retirer le préfixe "sha256="
  const signature = signatureHeader.replace('sha256=', '');

  // Calculer le HMAC-SHA256 du corps brut
  const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
  hmac.update(rawBody);
  const computedSignature = hmac.digest('hex');

  // Comparaison en temps constant pour éviter les timing attacks
  return crypto.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(computedSignature, 'hex'));
}

app.post('/webhooks/rodz', (req, res) => {
  const signatureHeader = req.headers['x-rodz-signature'];
  const rawBody = req.body; // Buffer grâce à express.raw()

  if (!verifySignature(rawBody, signatureHeader)) {
    console.error('Signature invalide, requête rejetée');
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // La signature est valide, traiter le signal
  const payload = JSON.parse(rawBody.toString());
  console.log('Signal vérifié :', payload.signal_type);

  res.status(200).json({ received: true });
});

app.listen(3000, () => {
  console.log('Serveur webhook en écoute sur le port 3000');
});

Points importants dans le code Node.js

  • express.raw() au lieu de express.json(). C’est la clé de l’implémentation. Le middleware express.json() parse le corps en objet JavaScript, et quand vous le sérialisez à nouveau avec JSON.stringify(), le résultat peut différer du corps original (espaces, ordre des clés). Le HMAC doit être calculé sur les octets exacts reçus.
  • crypto.timingSafeEqual() pour la comparaison. Ne comparez jamais les signatures avec ===. L’opérateur d’égalité standard s’arrête dès qu’il trouve une différence, ce qui permet à un attaquant de deviner la signature octet par octet en mesurant le temps de réponse. timingSafeEqual() compare les deux buffers en temps constant.
  • Le secret est lu depuis une variable d’environnement. Jamais codé en dur dans le code source.

Étape 4 : Implémenter la vérification en Python

Voici l’équivalent en Python avec Flask :

import hmac
import hashlib
import os
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = os.environ['RODZ_WEBHOOK_SECRET']


def verify_signature(raw_body: bytes, signature_header: str) -> bool:
    if not signature_header:
        return False

    # Retirer le préfixe "sha256="
    signature = signature_header.replace('sha256=', '')

    # Calculer le HMAC-SHA256 du corps brut
    computed = hmac.new(
        WEBHOOK_SECRET.encode('utf-8'),
        raw_body,
        hashlib.sha256
    ).hexdigest()

    # Comparaison en temps constant
    return hmac.compare_digest(signature, computed)


@app.route('/webhooks/rodz', methods=['POST'])
def handle_webhook():
    signature_header = request.headers.get('X-Rodz-Signature', '')
    raw_body = request.get_data()  # Corps brut, pas request.json

    if not verify_signature(raw_body, signature_header):
        return jsonify({'error': 'Invalid signature'}), 401

    # La signature est valide, traiter le signal
    payload = request.get_json()
    print(f"Signal vérifié : {payload['signal_type']}")

    return jsonify({'received': True}), 200


if __name__ == '__main__':
    app.run(port=3000)

Points importants dans le code Python

  • request.get_data() au lieu de request.json. Comme en Node.js, il faut travailler avec les octets bruts. request.json parse le JSON et vous perdez la correspondance exacte avec le corps original.
  • hmac.compare_digest() pour la comparaison. C’est l’équivalent Python de timingSafeEqual(). Cette fonction compare les deux chaînes en temps constant.
  • WEBHOOK_SECRET.encode('utf-8'). La fonction hmac.new() attend des bytes pour la clé. N’oubliez pas l’encodage.

Étape 5 : Rejeter les requêtes invalides

Votre endpoint doit renvoyer un code HTTP 401 Unauthorized quand la signature est absente ou invalide. Ne renvoyez jamais un code 200 si la vérification échoue, car Rodz interpréterait cela comme une livraison réussie et ne retenterait pas l’envoi.

Voici un récapitulatif des comportements attendus :

SituationCode HTTPComportement
Signature valide200Traiter le signal
Signature invalide401Rejeter la requête
Signature absente401Rejeter la requête
Erreur serveur500Rodz retentera la livraison

Erreurs courantes à éviter

Utiliser le JSON parsé au lieu du corps brut

C’est l’erreur la plus fréquente. Quand vous faites :

// NE FAITES PAS CECI
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(req.body)); // Mauvais !

Le résultat de JSON.stringify() peut différer du corps original. Les espaces, l’ordre des clés et l’encodage des caractères spéciaux peuvent changer. Utilisez toujours le corps brut tel que reçu par le serveur.

Utiliser une comparaison standard au lieu de la comparaison en temps constant

// NE FAITES PAS CECI
if (computedSignature === headerSignature) { // Vulnérable !

Les timing attacks sont des attaques réelles et exploitables. Un attaquant envoie des milliers de requêtes avec des signatures légèrement différentes et mesure le temps de réponse. Comme === s’arrête au premier octet différent, le temps de réponse révèle combien d’octets correspondent. Après suffisamment de tentatives, l’attaquant reconstruit la signature valide.

Utilisez crypto.timingSafeEqual() en Node.js ou hmac.compare_digest() en Python.

Stocker le secret en dur dans le code

// NE FAITES PAS CECI
const SECRET = 'mon_secret_en_dur'; // Visible dans Git !

Si votre code est dans un dépôt Git (même privé), le secret est exposé. Utilisez des variables d’environnement ou un gestionnaire de secrets (AWS Secrets Manager, HashiCorp Vault, etc.).

Oublier de valider le format du header

Vérifiez toujours que le header X-Rodz-Signature est présent et qu’il commence bien par sha256=. Sans cette vérification, votre code pourrait planter sur un header malformé ou absent.

Tester votre vérification manuellement

Pour valider votre implémentation, vous pouvez calculer la signature manuellement avec openssl et envoyer une requête de test avec cURL.

Calculer la signature avec openssl

# Définir le secret et le corps de la requête
SECRET="votre_secret_ici"
BODY='{"signal_type":"funding_round","company":{"name":"Acme Corp"}}'

# Calculer le HMAC-SHA256
SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')

echo "Signature : sha256=$SIGNATURE"

Envoyer la requête de test

curl -X POST http://localhost:3000/webhooks/rodz \
  -H "Content-Type: application/json" \
  -H "X-Rodz-Signature: sha256=$SIGNATURE" \
  -d "$BODY"

Si votre implémentation est correcte, le serveur doit répondre avec un code 200. Pour vérifier le rejet, modifiez un caractère de la signature et relancez la commande. Le serveur doit répondre 401.

Tester avec une signature invalide

curl -X POST http://localhost:3000/webhooks/rodz \
  -H "Content-Type: application/json" \
  -H "X-Rodz-Signature: sha256=0000000000000000000000000000000000000000000000000000000000000000" \
  -d "$BODY"

Votre serveur doit rejeter cette requête avec un code 401. Si ce n’est pas le cas, revérifiez votre implémentation.

Bonnes pratiques supplémentaires

Ajouter une vérification de timestamp

Pour renforcer la sécurité, vous pouvez vérifier l’âge de la requête. Rodz inclut un header X-Rodz-Timestamp contenant l’horodatage Unix de l’envoi. Rejetez les requêtes trop anciennes (par exemple, celles de plus de 5 minutes) pour vous protéger contre les attaques par rejeu.

const timestamp = parseInt(req.headers['x-rodz-timestamp'], 10);
const now = Math.floor(Date.now() / 1000);
const MAX_AGE = 300; // 5 minutes en secondes

if (Math.abs(now - timestamp) > MAX_AGE) {
  return res.status(401).json({ error: 'Request too old' });
}

Journaliser les tentatives échouées

Enregistrez chaque tentative de requête avec une signature invalide. Cela vous permet de détecter les tentatives d’attaque et d’identifier les problèmes de configuration. Incluez l’adresse IP source, le timestamp et les premiers caractères de la signature reçue (pas la totalité, pour éviter de logger des données potentiellement sensibles).

Faire tourner le secret périodiquement

Changez votre secret de webhook régulièrement (tous les 3 à 6 mois, par exemple). L’API Rodz vous permet de mettre à jour le secret d’un webhook existant sans le supprimer. Pendant la rotation, acceptez temporairement les deux secrets (ancien et nouveau) le temps de finaliser la transition.

Conformité RGPD et sécurité des données

La vérification des webhooks participe à votre conformité RGPD. En validant l’authenticité des requêtes, vous vous assurez que seules les données provenant de sources vérifiées entrent dans votre système. C’est une mesure technique de protection des données personnelles au sens de l’article 32 du RGPD. Pour en savoir plus sur les implications RGPD dans le contexte des signaux d’affaires, consultez notre guide RGPD et signaux d’affaires.

Questions fréquentes

Que se passe-t-il si je ne vérifie pas la signature ?

Votre endpoint accepte toutes les requêtes sans distinction. Un attaquant peut envoyer de faux signaux, déclencher vos workflows sur des données fictives, ou encore tester des injections sur votre serveur. C’est l’équivalent de laisser la porte d’entrée ouverte.

Le HMAC-SHA256 est-il suffisant pour sécuriser mes webhooks ?

Pour la grande majorité des cas d’usage, oui. HMAC-SHA256 garantit à la fois l’authenticité (la requête provient de Rodz) et l’intégrité (le contenu n’a pas été modifié). Combiné au HTTPS (qui chiffre le transport) et à la vérification du timestamp, c’est un niveau de sécurité solide.

Comment régénérer mon secret si je pense qu’il a été compromis ?

Mettez à jour le secret de votre webhook immédiatement via l’API Rodz avec une requête PATCH sur /v1/webhooks/{id}. Déployez le nouveau secret sur votre serveur dans la foulée. Les livraisons en cours avec l’ancien secret échoueront, mais Rodz les retentera automatiquement avec la nouvelle signature.

Pourquoi utiliser timingSafeEqual et pas simplement === ?

L’opérateur === compare les chaînes caractère par caractère et s’arrête dès qu’il trouve une différence. Le temps de réponse varie donc selon le nombre de caractères identiques au début des deux chaînes. Un attaquant peut exploiter cette fuite d’information pour reconstituer la signature correcte. timingSafeEqual effectue la comparaison en temps constant, quel que soit le nombre de correspondances.

Puis-je utiliser le même secret pour plusieurs webhooks ?

Techniquement, c’est possible. Mais c’est déconseillé. Si un secret est compromis, tous les webhooks qui l’utilisent deviennent vulnérables. Utilisez un secret unique par webhook pour limiter l’impact d’une éventuelle fuite.

Comment tester la vérification en environnement de développement ?

Utilisez la méthode openssl décrite dans la section “Tester votre vérification manuellement”. Vous pouvez aussi utiliser un outil comme ngrok pour exposer votre serveur local et recevoir de vrais webhooks Rodz en développement. Cela vous permet de tester avec des signatures réellement générées par Rodz.

La vérification HMAC ralentit-elle le traitement des webhooks ?

Non. Le calcul d’un HMAC-SHA256 prend quelques microsecondes, même sur du matériel modeste. C’est négligeable par rapport au temps de traitement du signal lui-même (écriture en base de données, appels API, etc.).

Que faire si la vérification échoue en production alors que tout fonctionnait avant ?

Vérifiez trois choses dans l’ordre : (1) le secret n’a pas été modifié côté Rodz ou côté serveur, (2) votre middleware ne modifie pas le corps de la requête avant la vérification (compression, transformation, parsing), (3) il n’y a pas de proxy intermédiaire qui altère les headers ou le corps de la requête. Si le problème persiste, consultez la documentation API Rodz ou contactez le support.

Pour aller plus loin

La sécurisation de vos webhooks est une étape fondamentale pour bâtir une intégration fiable avec Rodz. Maintenant que vos endpoints sont protégés, vous pouvez construire des workflows automatisés en toute confiance.

Pour approfondir votre maîtrise de l’API Rodz :

Partager :

Générez votre stratégie outbound gratuitement

Notre IA analyse votre entreprise et crée un playbook complet : ICP, personas, templates d'emails, scripts d'appels.

Générer ma stratégie