Todos los artículos
Eventos de push y merge request de un repositorio Git que cruzan un túnel HTTPS firmado hacia un servicio de desarrollo local.
GitLabDevOpswebhookslocalhost

Probar webhooks de GitLab en localhost de forma segura

Para probar un webhook de GitLab en localhost, exponga su manejador local a través de un túnel HTTPS, agregue esa URL en Configuración → Webhooks, genere un token de firma y verifique la firma de Webhooks Estándar de GitLab antes de analizar la carga útil. Active un push o merge request, inspeccione la entrega e itere localmente sin desplegar su integración después de cada cambio.

Use tokens de firma de GitLab, no un nuevo token secreto en texto plano.

GitLab admite dos mecanismos que son fáciles de confundir. El token secreto más antiguo se copia en el encabezado de la X-Gitlab-Token solicitud. Prueba el conocimiento de un valor compartido, pero no protege la integridad del cuerpo. GitLab ahora recomienda un token de firma para nuevos webhooks. Produce una firma HMAC-SHA256 y sigue el formato de mensaje de los Webhooks Estándar.

El La documentación oficial de webhooks de GitLab dice que una solicitud firmada contiene webhook-id, webhook-timestamp y webhook-signature. La firma cubre el ID del mensaje, la marca de tiempo y el cuerpo JSON exacto en bruto. Esto protege tanto el origen como la integridad de la carga útil.

Implementa la verificación estándar de Webhooks en Node.js

Los tokens de firma de GitLab se muestran una vez y usan un prefijo whsec_ . Elimina ese prefijo y decodifica en Base64 el resto para obtener la clave HMAC. Cada firma recibida tiene la forma v1,<base64 signature>; el encabezado puede contener varias firmas separadas por espacios.

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 ventana de marca de tiempo de cinco minutos mostrada aquí es una política de la aplicación, no un valor para copiar ciegamente. Elige una tolerancia que acomode el desfase del reloj pero bloquee la reproducción útil. Sincroniza el reloj de la máquina receptora. Almacena cada webhook-id bajo una restricción única porque una verificación de marca de tiempo nueva por sí sola no puede prevenir dos entregas inmediatas del mismo mensaje.

Construye la ruta de webhook de Express

Captura el cuerpo en crudo en esta ruta. Una llamada global express.json() antes de la verificación destruye la representación byte a byte firmada por 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);

Monta el analizador JSON normal después de la ruta del webhook o usa su verify callback para preservar un buffer en crudo. Nunca desactives la verificación de firmas solo porque el endpoint reenvíe a localhost; la URL del túnel todavía es accesible desde internet público.

Crea un endpoint público HTTPS

  1. Inicia la integración localmente y prueba su ruta con una solicitud deliberadamente sin firmar. Debe devolver 401, demostrando que la autenticación está activa.
  2. Ejecuta npx portpreview 3000 en otra terminal.
  3. Copia el origen HTTPS y añade /webhooks/gitlab.
  4. Mantenga el túnel abierto durante la configuración y las pruebas de eventos.

La verificación SSL de GitLab debe permanecer habilitada. Un túnel con TLS confiable públicamente evita errores de certificados autofirmados. Si GitLab se ejecuta en una red privada autogestionada, también debe tener acceso saliente a la URL pública del túnel.

Configure el webhook del proyecto

  1. Abra el proyecto de GitLab y elija Configuración → Webhooks.
  2. Seleccione Agregar nuevo webhook y pegue la URL completa de entrega del túnel.
  3. Seleccione Generar token de firma, copie el token inmediatamente y guárdelo en GITLAB_WEBHOOK_SIGNING_TOKEN.
  4. Seleccione solo los desencadenadores necesarios, por ejemplo, eventos de Push, eventos de Merge request, eventos de Push de etiquetas o eventos de Pipeline.
  5. Deje la verificación SSL habilitada y guarde el webhook.
  6. Utiliza la acción de prueba de GitLab o produce un evento real, luego inspecciona la solicitud local y el historial de entrega de GitLab.

Reinicia el proceso local después de configurar la variable de entorno. Si estás migrando una integración existente, GitLab permite un token de firma y un token secreto heredado juntos. Verifica webhook-signature cuando esté presente, recurre temporalmente a X-Gitlab-Token, luego elimina el token más débil después de que todos los receptores admitan firmas.

Distribuye los eventos de GitLab por encabezado y payload

X-Gitlab-Event proporciona un nombre de evento legible como Push Hook o Merge Request Hook. Úsalo para la enrutación, pero valida también object_kind del payload. Esto hace que las combinaciones inesperadas sean visibles.

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);
}

Eventos de push

Prueba de creación de ramas, commits ordinarios, empujes forzados y eliminación de ramas. Un SHA cero puede representar un lado faltante de una transición de referencia. Los empujes grandes pueden diferir de un fixture de un solo commit, por lo que no asuma que cada commit cambiado aparece en un arreglo ilimitado. Use identificadores de proyecto y de referencia en lugar de analizar una cadena de visualización.

Eventos de solicitudes de fusión

Acciones como abrir, actualizar, aprobar, fusionar y cerrar pueden compartir el mismo tipo de evento general. Rutee según los atributos documentados del objeto y haga que las actualizaciones repetidas sean idempotentes. Nunca fusione código ni apruebe un despliegue únicamente porque un título o nombre de usuario mutable coincida.

Eventos de pipeline y de trabajo

Estos pueden ser frecuentes. Filtre en GitLab y nuevamente en su manejador por proyecto, rama, estado y entorno. Ponga en cola trabajos lentos de artefactos o despliegue y reconozca primero el webhook.

Diseño para reintentos y desencadenadores recursivos

GitLab incluye webhook-id, que permanece consistente a través de los reintentos y equivale al Idempotency-Key heredado. Úsalo como la clave de idempotencia de entrega. X-Gitlab-Webhook-UUID identifica la ejecución de un webhook, mientras que X-Gitlab-Event-UUID puede ayudar a rastrear eventos; los webhooks recursivos pueden compartir el UUID del evento.

Si el manejador modifica GitLab a través de la API, puede crear otro webhook. Agrega prevención explícita de bucles: etiqueta las acciones con la identidad de tu integración, ignora cambios que no alteren el estado deseado, y limita las transiciones del flujo de trabajo. La guía de reintentos e idempotencia cubre patrones de bandeja de entrada transaccionales.

Soluciona problemas de pruebas fallidas del webhook de GitLab

GitLab no puede conectarse a la URL

Confirma que el proceso del túnel está activo, que la ruta completa es correcta y que tu servidor local escucha en el puerto reenviado. Para GitLab autogestionado, inspecciona la política de red saliente y el DNS. No desactives la verificación SSL para ocultar un fallo de enrutamiento no relacionado.

La firma nunca coincide

Usa el token de firma, no el antiguo token secreto. Elimina whsec_, decodifica en Base64 el token restante y firma {webhook-id}.{webhook-timestamp}.{raw body}. Codifica en Base64 el digest binario HMAC y prefíjalo con v1,. Compáralo con cada firma separada por espacios.

La marca de tiempo es rechazada

Verifica la hora del sistema y el manejo de la zona horaria; el encabezado es una marca de tiempo Unix en segundos. No la compares con milisegundos de JavaScript sin dividir entre 1000. Si estás depurando una solicitud antigua capturada, el rechazo de la marca de tiempo es una protección de repetición correcta.

GitLab deshabilita o ralentiza el webhook

Inspecciona el estado de entrega reciente y la respuesta de tu ruta. Devuelve 2xx rápidamente después de la aceptación duradera. Repetidos 401 significa que la configuración del token es incorrecta; repetidos 5xx significa fallos del manejador; los tiempos de espera indican demasiado trabajo sincrónico.

Solo llegan algunos eventos

Revisa los disparadores seleccionados y los filtros de ramas. Los webhooks de grupo y proyecto tienen diferente alcance. Confirma que el evento ocurrió en el proyecto exacto donde se configuró este webhook.

Mantén seguros los datos locales del webhook de GitLab

  • Almacena los tokens de firma solo en archivos de entorno ignorados y rota cualquier token filtrado.
  • Valida firmas, marcas de tiempo, IDs de proyectos y tipos de eventos permitidos antes de efectos secundarios.
  • Redacta mensajes de commit, URLs de repositorios privados, correos de usuarios y variables de CI de las capturas.
  • Proporcione al token de API de integración solo los permisos necesarios para su acción posterior.
  • Elimine el historial de cargas locales cuando finalice la prueba.

Para diagnósticos independientes del proveedor, use la guía de depuración de webhooks locales. GitHub utiliza un formato de firma diferente, así que consulte la guía de webhooks de GitHub por separado en lugar de reutilizar su verificador.

Preguntas frecuentes

¿Cómo pruebo un webhook de GitLab en localhost?
Expón tu ruta local con un túnel HTTPS, agrega su URL pública en Webhooks del proyecto de GitLab, configura un token de firma y disparadores, luego genera un evento de prueba o real.
¿Los nuevos webhooks de GitLab deben usar X-Gitlab-Token?
GitLab recomienda tokens de firma para los nuevos webhooks. X-Gitlab-Token lleva un secreto en texto plano, mientras que los tokens de firma autentican un digest HMAC-SHA256 de la solicitud.
¿Cómo se calcula la firma del webhook de GitLab?
Decodifica el token de firma después de eliminar whsec_, aplica HMAC-SHA256 a la cadena webhook-id.webhook-timestamp.raw-body, codifica en Base64 el resumen y antepón v1,.
¿Cómo evito acciones duplicadas de webhooks en GitLab?
Almacena webhook-id bajo una restricción única y aplica los efectos secundarios de manera transaccional. GitLab mantiene ese ID estable durante los reintentos, lo que lo hace adecuado para la idempotencia.