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
- 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.
- Exécuter
npx portpreview 3000dans un autre terminal. - Copier l'origine HTTPS et ajouter
/webhooks/gitlab. - 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
- Ouvrez le projet GitLab et choisissez Paramètres → Webhooks.
- Sélectionnez Ajouter un nouveau webhook et collez l'URL complète de livraison du tunnel.
- Sélectionnez Générer un jeton de signature, copiez immédiatement le jeton et enregistrez-le dans
GITLAB_WEBHOOK_SIGNING_TOKEN. - 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.
- Laissez la vérification SSL activée et enregistrez le webhook.
- 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.
