Tous les articles
Les événements de push et de demande de fusion du dépôt Git traversant un tunnel HTTPS signé vers un service de développement local.
GitLabDevOpswebhookslocalhost

Tester les webhooks GitLab en toute sécurité sur localhost

Pour tester un webhook GitLab sur localhost, exposez votre gestionnaire local via un tunnel HTTPS, ajoutez cette URL dans Paramètres → Webhooks, générez un jeton de signature et vérifiez la signature des Webhooks Standard de GitLab avant d’analyser la charge utile. Déclenchez une demande de push ou de fusion, inspectez la livraison, et itérez localement sans déployer votre intégration après chaque changement.

Utilisez des jetons de signature GitLab, pas un nouveau jeton secret en texte clair

GitLab prend en charge deux mécanismes qui sont faciles à confondre. L'ancien jeton secret est copié dans le X-Gitlab-Token entête de requête. Il prouve la connaissance d’une valeur partagée mais ne protège pas l’intégrité du corps. GitLab recommande maintenant un jeton de signature pour les nouveaux webhooks. Il produit une signature HMAC-SHA256 et suit le format de message standard des Webhooks.

La documentation officielle des webhooks GitLab indique qu'une requête signée contient webhook-id, webhook-timestamp, et webhook-signature. La signature couvre l'ID du message, le timestamp et le corps JSON brut exact. Cela protège à la fois l'origine et l'intégrité de la charge utile.

Mettre en œuvre la vérification standard des webhooks dans Node.js

Les jetons de signature GitLab sont affichés une seule fois et utilisent un préfixe whsec_ . Supprimez ce préfixe et décodez le reste en Base64 pour obtenir la clé HMAC. Chaque signature reçue a la forme v1,<base64 signature> ; l'en-tête peut contenir plusieurs signatures séparées par des espaces.

import crypto from 'node:crypto';

function safeEqual(a, b) {
  const left = Buffer.from(a);
  const right = Buffer.from(b);
  return left.length === right.length &&
    crypto.timingSafeEqual(left, right);
}

function verifyGitLabWebhook({ token, id, timestamp, body, signatures }) {
  if (!token?.startsWith('whsec_') || !id || !timestamp || !signatures) {
    return false;
  }

  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!Number.isFinite(age) || age > 300) return false;

  const key = Buffer.from(token.slice(6), 'base64');
  const message = `${id}.${timestamp}.${body}`;
  const digest = crypto.createHmac('sha256', key).update(message).digest('base64');
  const expected = `v1,${digest}`;
  return signatures.split(' ').some((value) => safeEqual(value, expected));
}

La fenêtre de timestamp de cinq minutes présentée ici est une politique d'application, pas une valeur à copier aveuglément. Choisissez une tolérance qui prend en compte le décalage d'horloge mais bloque les rejouages utiles. Synchronisez l'horloge de la machine réceptrice. Stockez chaque webhook-id sous une contrainte unique car une simple vérification de l'horodatage récent ne peut pas empêcher deux livraisons immédiates du même message.

Créer la route webhook Express

Capturer le corps brut sur cette route. Un appel global express.json() avant la vérification détruit la représentation octet pour octet signée par GitLab.

import express from 'express';

const app = express();
app.post(
  '/webhooks/gitlab',
  express.raw({ type: 'application/json', limit: '2mb' }),
  async (req, res) => {
    const body = req.body.toString('utf8');
    const valid = verifyGitLabWebhook({
      token: process.env.GITLAB_WEBHOOK_SIGNING_TOKEN,
      id: req.get('webhook-id'),
      timestamp: req.get('webhook-timestamp'),
      signatures: req.get('webhook-signature'),
      body,
    });
    if (!valid) return res.sendStatus(401);

    await inbox.insertOnce(req.get('webhook-id'), JSON.parse(body));
    return res.sendStatus(202);
  },
);
app.use(express.json());
app.listen(3000);

Monter le parseur JSON normal après la route webhook ou utiliser son verify rappel pour préserver un tampon brut. Ne désactivez jamais les vérifications de signature simplement parce que le point de terminaison transfère vers localhost ; l'URL du tunnel est toujours accessible depuis Internet public.

Créer un point de terminaison HTTPS public

  1. Démarrer l'intégration localement et tester sa route avec une requête volontairement non signée. Elle devrait renvoyer 401, prouvant que l'authentification est active.
  2. Exécuter npx portpreview 3000 dans un autre terminal.
  3. Copier l'origine HTTPS et ajouter /webhooks/gitlab.
  4. Gardez le tunnel ouvert pendant toute la configuration et les tests d'événements.

La vérification SSL de GitLab doit rester activée. Un tunnel avec TLS de confiance publique évite les erreurs de certificat auto-signé. Si GitLab fonctionne dans un réseau privé autogéré, il doit également avoir accès en sortie à l'URL publique du tunnel.

Configurer le webhook du projet

  1. Ouvrez le projet GitLab et choisissez Paramètres → Webhooks.
  2. Sélectionnez Ajouter un nouveau webhook et collez l'URL complète de livraison du tunnel.
  3. Sélectionnez Générer un jeton de signature, copiez immédiatement le jeton et enregistrez-le dans GITLAB_WEBHOOK_SIGNING_TOKEN.
  4. Sélectionnez uniquement les déclencheurs requis — par exemple, événements de push, événements de merge request, événements de push de tag ou événements de pipeline.
  5. Laissez la vérification SSL activée et enregistrez le webhook.
  6. Utilisez l'action de test de GitLab ou produisez un événement réel, puis inspectez la requête locale et l'historique de livraison de GitLab.

Redémarrez le processus local après avoir défini la variable d'environnement. Si vous migrez une intégration existante, GitLab permet d'utiliser un jeton de signature et un jeton secret hérité ensemble. Vérifiez webhook-signature lorsqu'il est présent, revenez temporairement à X-Gitlab-Token, puis supprimez le secret le plus faible une fois que tous les récepteurs prennent en charge les signatures.

Distribuez les événements GitLab par en-tête et charge utile

X-Gitlab-Event donne un nom d'événement lisible tel que Push Hook ou Merge Request Hook. Utilisez-le pour le routage, mais validez également la object_kind de la charge utile. Cela rend visibles les combinaisons inattendues.

switch (req.get('x-gitlab-event')) {
  case 'Push Hook':
    await handlePush(payload);
    break;
  case 'Merge Request Hook':
    await handleMergeRequest(payload);
    break;
  case 'Pipeline Hook':
    await handlePipeline(payload);
    break;
  default:
    await recordUnsupportedGitLabEvent(payload.object_kind);
}

Événements de push

Création de branche de test, commits ordinaires, pushes forcés et suppression de branche. Un SHA nul peut représenter un côté manquant d'une transition de ref. Les pushes volumineux peuvent différer d'un fixture à un commit, donc ne supposez pas que chaque commit modifié apparaîtra dans un tableau illimité. Utilisez des identifiants de projet et de ref plutôt que de parser une chaîne d'affichage.

Événements de demande de fusion

Des actions telles que ouvrir, mettre à jour, approuver, fusionner et fermer peuvent partager le même type d'événement général. Orientez-vous selon les attributs d'objet documentés et rendez les mises à jour répétées idempotentes. Ne fusionnez jamais du code ni n'approuvez un déploiement simplement parce qu'un titre ou un nom d'utilisateur mutable correspond.

Événements de pipeline et de travail

Ceux-ci peuvent être fréquents. Filtrez d'abord sur GitLab, puis de nouveau dans votre gestionnaire par projet, branche, statut et environnement. Mettez en file d'attente le travail sur artefacts ou déploiements lents et reconnaissez d'abord le webhook.

Conception pour les nouvelles tentatives et les déclencheurs récursifs

GitLab inclut webhook-id, qui reste cohérent lors des nouvelles tentatives et équivaut à l'ancien Idempotency-Key. Utilisez-le comme clé d'idempotence de livraison. X-Gitlab-Webhook-UUID identifie une exécution de webhook, tandis que X-Gitlab-Event-UUID peut aider à tracer les événements ; les webhooks récursifs peuvent partager l'UUID de l'événement.

Si le gestionnaire modifie GitLab via l'API, il peut créer un autre webhook. Ajoutez une prévention explicite des boucles : marquez les actions avec l'identité de votre intégration, ignorez les modifications qui n'altèrent pas l'état souhaité, et limitez les transitions de workflow. Le guide sur les nouvelles tentatives et l'idempotence couvre les modèles de boîte de réception transactionnelle.

Dépannage des tests de webhook GitLab échoués

GitLab ne peut pas se connecter à l'URL

Confirmez que le processus de tunnel est actif, que le chemin complet est correct et que votre serveur local écoute sur le port transféré. Pour GitLab auto-hébergé, inspectez la politique réseau sortante et le DNS. Ne désactivez pas la vérification SSL pour masquer une erreur de routage non liée.

La signature ne correspond jamais

Utilisez le jeton de signature, pas l'ancien jeton secret. Supprimez whsec_, décodez en Base64 le jeton restant, et signez {webhook-id}.{webhook-timestamp}.{raw body}. Encodez en Base64 le digest HMAC binaire et préfixez-le avec v1,. Comparez-le avec chaque signature séparée par des espaces.

Le timestamp est rejeté

Vérifiez l'heure système et la gestion du fuseau horaire ; l'en-tête est un timestamp Unix en secondes. Ne le comparez pas aux millisecondes JavaScript sans le diviser par 1000. Si vous déboguez une ancienne requête capturée, le rejet du timestamp est une protection correcte contre la répétition.

GitLab désactive ou réduit le webhook

Vérifiez le statut de livraison récent et la réponse de votre route. Retournez rapidement 2xx après une acceptation durable. Une répétition de 401 signifie que la configuration du token est incorrecte ; une répétition de 5xx signifie des échecs du gestionnaire ; les délais d'attente indiquent trop de travail synchrone.

Seulement certains événements arrivent

Examinez les déclencheurs et filtres de branche sélectionnés. Les webhooks de groupe et de projet ont des portées différentes. Confirmez que l'événement a eu lieu dans le projet exact où ce webhook est configuré.

Gardez les données de webhook GitLab locales sécurisées

  • Stockez les tokens de signature uniquement dans des fichiers d'environnement ignorés et faites pivoter tout token divulgué.
  • Validez les signatures, horodatages, identifiants de projet et types d'événements autorisés avant les effets secondaires.
  • Censurez les messages de commit, URL de dépôts privés, emails d'utilisateur et variables CI à partir des captures.
  • Donnez au jeton API d'intégration uniquement les permissions nécessaires pour son action en aval.
  • Supprimez l'historique local des charges utiles à la fin des tests.

Pour des diagnostics indépendants du fournisseur, utilisez le guide de débogage des webhooks locaux. GitHub utilise un format de signature différent, donc consultez le guide des webhooks GitHub séparé au lieu de réutiliser son vérificateur.

Questions fréquentes

Comment tester un webhook GitLab sur localhost ?
Exposez votre route locale avec un tunnel HTTPS, ajoutez son URL publique sous Webhooks du projet GitLab, configurez un token de signature et les déclencheurs, puis générez un événement de test ou réel.
Les nouveaux webhooks GitLab doivent-ils utiliser X-Gitlab-Token ?
GitLab recommande les tokens de signature pour les nouveaux webhooks. X-Gitlab-Token contient un secret en texte clair, tandis que les tokens de signature authentifient un digest HMAC-SHA256 de la requête.
Comment est calculée la signature du webhook de GitLab ?
Décodez le jeton de signature après avoir retiré whsec_, appliquez HMAC-SHA256 sur la chaîne webhook-id.webhook-timestamp.raw-body, encodez le résumé en Base64, et préfixez-le avec v1,.
Comment prévenir les actions de webhook GitLab en double ?
Stockez webhook-id sous une contrainte d'unicité et appliquez les effets secondaires de manière transactionnelle. GitLab garde cet ID stable lors des réessais, ce qui le rend adapté pour l'idempotence.