Cada sprint empieza con un tablero lleno de tickets y un equipo que necesita un espacio limpio donde trabajar. Para las agencias que gestionan proyectos de WordPress de clientes en ciclos de dos semanas, esto significa crear un entorno staging en MyKinsta antes de que se atienda el primer ticket.

Esto lleva unos minutos, pero es el tipo de tarea que se olvida porque parece trivial.

La API de Kinsta puede eliminar ese paso. Cuando se inicia un sprint en Jira, puedes configurar un webhook que active un evento en el middleware, el cual lee el payload, la asigna a un sitio de Kinsta y llama a la API para crear un nuevo entorno staging.

Por qué las agencias de WordPress deberían automatizar la creación de entornos

Crear un entorno después de planificar un sprint implica abrir MyKinsta, buscar el sitio del cliente adecuado entre una lista de docenas, crear y nombrar un entorno, y luego volver a Jira. Aunque no es complicado, hay que hacerlo en el momento adecuado, siempre, y para cada proyecto de cliente en ejecución.

Si te lo saltas, el equipo empieza a trabajar en el entorno del último sprint. A partir de ahí, los cambios se van acumulando unos sobre otros, y cuando surge un error, localizarlo se parece más a la arqueología que a la depuración.

Lo que necesitas antes de empezar

Para conectar la API de Kinsta y Jira, necesitas una cuenta de Kinsta con al menos un sitio de WordPress en un entorno existente, una cuenta de Jira Cloud con acceso de administrador para configurar webhooks, y Node.js instalado localmente.

Para autenticarte con la API de Kinsta, ve a [Tu empresa] > Configuración de la empresa > Claves API en MyKinsta y haz clic en Crear clave API.

El panel de control de MyKinsta muestra la pantalla de claves API, que incluye dos claves API existentes, junto con un botón para crear una nueva.
El panel de MyKinsta mostrando las Claves API.

A continuación, dale un nombre a la clave, establece una duración de caducidad y haz clic en Generar. La clave se muestra una sola vez, así que anótala antes de continuar.

Colócala en un archivo .env en la raíz del proyecto junto al ID de tu empresa Kinsta, que encontrarás en Configuración de la empresa > Detalles de facturación:

KINSTA_API_KEY=your_api_key_here
KINSTA_COMPANY_ID=your_company_id_here

Obtén los IDs de tus sitios Jira y Kinsta

Necesitas el ID del sitio de Kinsta para cada proyecto de cliente en la automatización. Se trata de un UUID que Kinsta asigna al crear el sitio. Aparece en la URL de MyKinsta cuando abres un sitio o al realizar la llamada GET /sites una vez que tengas tu clave API configurada:

https://my.kinsta.com/sites/details/fbab4927-e354-4044-b226-29ac0fbd20ca/…

En cuanto a Jira, necesitas el ID numérico del tablero de cada proyecto que quieras conectar. Aparece en la URL (aquí aparece como 2):

https://your-domain.atlassian.net/jira/software/projects/SCRUM/boards/2

Es el mismo valor que Jira incluye en el payload del webhook sprint_started como originBoardId. La asignación de los IDs de tablero a los IDs de sitio se encuentra en tu archivo .env:

BOARD_ID_CLIENT_A=2
SITE_ID_CLIENT_A=fbab4927-e354-4044-b226-29ac0fbd20ca
BOARD_ID_CLIENT_B=5
SITE_ID_CLIENT_B=44b5a6d1-c83f-4b0e-9a1c-2e7dbc903fa1

Además, para el desarrollo local, Jira no puede conectarse directamente a localhost. Puedes usar Ngrok para exponer un puerto local a Internet con una URL pública temporal, que podrás utilizar como endpoint de tu webhook durante el desarrollo. Una vez que tengas la dirección del middleware desplegada, podrás sustituirla.

Cómo automatizar la configuración del entorno de sprint con Jira y la API de Kinsta

Esta integración se ejecuta a través de dos sistemas. En Jira, un webhook se dispara cuando comienza un sprint y envía el payload del evento a tu middleware. En Kinsta, el middleware lee el ID del tablero incluido en el payload, lo asocia a un ID de sitio mediante el mapa de configuración, y llama a la API de Kinsta para crear un entorno staging simple con el nombre del sprint.

1. Registra un webhook de Jira para eventos de sprint

Jira Cloud te ofrece dos formas de registrar un webhook. La opción más sencilla para la mayoría de los equipos es a través de la interfaz de usuario de Jira. La opción Configuración > Sistema está dentro del menú superior derecho:

La opción Sistema de Jira Cloud dentro del menú de configuración general de la interfaz de usuario de Jira.
La opción Sistema de Jira Cloud dentro de la configuración general.

Desde ahí, elige Avanzado > WebHooks, y luego haz clic en Crear un WebHook:

El panel de administración de Jira Cloud, en el que se ve la sección WebHooks con el botón Crear un WebHook en la esquina superior derecha.
Sección de WebHooks con un botón Crear un WebHook en la parte superior derecha.

Aquí, escribe un nombre, pega una URL de middleware con /sprint añadido al final (por ahora basta con una opción ficticia) y, en Eventos, selecciona Sprint > iniciado. Esto crea un webhook de administración que se activa cada vez que se produce un evento sprint_started en toda tu instancia de Jira.

El formulario de creación de webhooks de Jira, en el que se muestran los campos Nombre y URL, así como la sección Eventos, con la opción Sprint iniciado seleccionada.
El formulario de creación del webhook de Jira.

La segunda opción es la API REST, utilizando POST /rest/webhooks/1.0/webhook. Esto funciona bien cuando el registro de webhooks forma parte de un script de despliegue:

curl -X POST \
  https://your-domain.atlassian.net/rest/webhooks/1.0/webhook \
  -u [email protected]:your-api-token \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Sprint provisioning webhook",
    "url": "https://your-middleware-url.com/sprint",
    "events": ["sprint_started"],
    "filters": {},
    "excludeBody": false
  }'

Al llamar a PUT /rest/webhooks/1.0/webhook/refresh, se amplía el plazo de caducidad de 30 días del webhook. Cuando Jira activa sprint_started, el payload llega a tu endpoint como un POST JSON con la siguiente estructura:

{
  "timestamp": 1705431600000,
  "webhookEvent": "sprint_started",
  "sprint": {
    "id": 15,
    "self": "https://your-domain.atlassian.net/rest/agile/1.0/sprint/15",
    "state": "active",
    "name": "Sprint 12",
    "startDate": "2026-02-02T00:00:00.000Z",
    "endDate": "2026-02-27T00:00:00.000Z",
    "originBoardId": 2,
    "goal": "Complete payment processing improvements"
  }
}

El middleware utiliza sprint.originBoardId para buscar el ID del sitio Kinsta y sprint.name para nombrar el nuevo entorno: cada evento sprint_started dentro de tu instancia de Jira llega al endpoint. La búsqueda del ID del tablero en el mapa de configuración es lo que asigna la automatización al proyecto cliente correcto e ignora todo lo demás.

2. Construye el endpoint del middleware

Una vez creado el webhook, debes inicializar un nuevo proyecto Node.js e instalar Express.js junto con dotenv:

npm init -y
npm install express dotenv

express se encarga del enrutamiento y de procesar las peticiones, mientras que dotenv carga tu archivo .env. Necesitas crear app.js para configurar el servidor. Aquí tienes el archivo completo:

// app.js
const express = require('express');
const crypto = require('crypto');
require('dotenv').config();
const app = express();

// Raw body parser on the /sprint route enables HMAC signature verification
app.use('/sprint', express.raw({ type: 'application/json' }));
app.use(express.json());

const KinstaAPIUrl = 'https://api.kinsta.com/v2';
const headers = {
  'Content-Type': 'application/json',
  Authorization: `Bearer ${process.env.KINSTA_API_KEY}`
};

// Board ID to Kinsta site ID config map
const siteConfig = {
  [process.env.BOARD_ID_CLIENT_A]: process.env.SITE_ID_CLIENT_A,
  [process.env.BOARD_ID_CLIENT_B]: process.env.SITE_ID_CLIENT_B,
};

function verifyJiraSignature(req) {
  const signature = req.headers['x-hub-signature'];
  const secret = process.env.JIRA_WEBHOOK_SECRET;
  if (!signature || !secret) return false;
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(req.body)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

app.post('/sprint', async (req, res) => {
  if (!verifyJiraSignature(req)) {
    return res.status(401).json({ message: 'Invalid signature' });
  }

  const body = JSON.parse(req.body);
  const { webhookEvent, sprint } = body;

  if (webhookEvent !== 'sprint_started') {
    return res.status(200).json({ message: 'Event ignored' });
  }

  const boardId = String(sprint.originBoardId);
  const siteId = siteConfig[boardId];

  if (!siteId) {
    console.log(`No site configured for board ${boardId}`);
    return res.status(200).json({ message: 'Board not mapped' });
  }

  // Kinsta API calls added in the steps below
  res.status(200).json({ message: 'Received' });
});

app.listen(3000, () => console.log('Middleware running on port 3000'));

Para proteger el endpoint, debes generar una clave secreta durante la configuración del webhook. Jira permite que esto sea opcional durante la configuración, pero es prácticamente imprescindible para garantizar la seguridad de la instancia.

Seguridad del endpoint

Jira firma cada payload e incluye el resultado en el encabezado X-Hub-Signature como sha256=<hash>. Añade el secreto a tu archivo .env junto con el resto de credenciales:

JIRA_WEBHOOK_SECRET=your_webhook_secret_here

La función de verificación se encuentra en app.js y utiliza el módulo crypto integrado en Node. Lee la firma del encabezado de la solicitud, calcula el HMAC esperado a partir del cuerpo sin procesar de la solicitud y utiliza timingSafeEqual para compararlos de una forma que evite los ataques de sincronización. Aquí tienes la parte relevante de app.js:

const crypto = require('crypto');

function verifyJiraSignature(req) {
  const signature = req.headers['x-hub-signature'];
  const secret = process.env.JIRA_WEBHOOK_SECRET;
  if (!signature || !secret) return false;
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(req.body)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Esta función es lo primero que se ejecuta dentro del controlador de ruta POST /sprint. Si la verificación falla, el middleware devuelve 401 inmediatamente y no se ejecuta nada más:

app.post('/sprint', async (req, res) => {
  if (!verifyJiraSignature(req)) {
    return res.status(401).json({ message: 'Invalid signature' });
  }

  const body = JSON.parse(req.body);
  // …rest of the handler
});

La ruta utiliza express.raw() en la ruta /sprint porque verifyJiraSignature lo necesita para calcular el HMAC. Una vez pasada la verificación, JSON.parse(req.body) da el mismo resultado que tendría express.json().

3. Autenticarse con la API de Kinsta y recuperar los entornos del sitio

Todas las solicitudes a la API de Kinsta usan autenticación con token Bearer: la constante headers en app.js se encarga de eso en cada solicitud de la aplicación. La línea require(“dotenv”).config() al principio garantiza que la clave se cargue desde .env antes de que se ejecute nada más, así que nunca aparece en el código fuente propiamente dicho.

Kinsta utiliza IDs de entorno en lugar de IDs de sitio para el endpoint de aprovisionamiento, así que deberías añadir la función getEnvironmentId debajo de la constante headers:

const getEnvironmentId = async (siteId) => {
  const resp = await fetch(
    `${KinstaAPIUrl}/sites/${siteId}/environments`,
    { method: 'GET', headers }
  );
  const data = await resp.json();
  return data.site.environments[0].id;
};

Esto llama a GET /sites/{siteId}/environments y devuelve el ID del primer entorno (es decir, en producción) en la respuesta. Si un sitio utiliza varios entornos y necesitas seleccionar uno en concreto, compara con el nombre del entorno en lugar de tomar el primer resultado.

4. Crea un entorno staging básico usando la API de Kinsta

Con los IDs del sitio y del entorno resueltos, el middleware llama a POST /sites/{siteId}/environments/plain para crear el entorno sprint. Esto se hace mediante la función createSprintEnvironment que se encuentra debajo de getEnvironmentId:

const createSprintEnvironment = async (siteId, sprintName) => {
  const resp = await fetch(
    `${KinstaAPIUrl}/sites/${siteId}/environments/plain`,
    {
      method: 'POST',
      headers,
      body: JSON.stringify({
        display_name: sprintName,
        is_premium: false
      })
    }
  );
  const data = await resp.json();
  return data;
};

display_name es lo que aparece en MyKinsta, mientras que si usas sprint.namedirectamente desde el payload de Jira, cada entorno del panel se corresponde con el sprint al que pertenece. El indicador is_premium determina si Kinsta lo configura como un entorno staging estándar o premium. Si lo pones en false, se crea un entorno estándar.

Cuando la solicitud llega a Kinsta, devuelve 202 Accepted con un operation_id en lugar de un entorno completado:

{
  "operation_id": "environments:add-plain-54fb80af-576c-4fdc-ba4f-b596c83f15a1",
  "message": "Adding plain environment in progress",
  "status": 202
}

El procesamiento async de Kinsta evita que el hilo de la solicitud se bloquee mientras se completa el aprovisionamiento. El operation_id es lo que debes pasar al endpoint para seguir el progreso. A continuación, actualiza la ruta POST /sprint para llamar a ambas funciones de forma secuencial:

app.post('/sprint', async (req, res) => {
  if (!verifyJiraSignature(req)) {
    return res.status(401).json({ message: 'Invalid signature' });
  }

  const body = JSON.parse(req.body);
  const { webhookEvent, sprint } = body;

  if (webhookEvent !== 'sprint_started') {
    return res.status(200).json({ message: 'Event ignored' });
  }

  const boardId = String(sprint.originBoardId);
  const siteId = siteConfig[boardId];

  if (!siteId) {
    console.log(`No site configured for board ${boardId}`);
    return res.status(200).json({ message: 'Board not mapped' });
  }
try {
    const envId = await getEnvironmentId(siteId);
    const result = await createSprintEnvironment(siteId, sprint.name);
    res.status(200).json(result);
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Environment creation failed' });
  }
});

Utilizar el bloque try es más limpio que depender de múltiples sentencias if. Sin embargo, mantén la verificación de la firma de Jira al principio del archivo, ya que debe ejecutarse antes que cualquier otro código.

5. Consulta el estado de la operación y confirma el aprovisionamiento

Para comprobar cuándo se ha completado, consulta periódicamente /operations/{operation_id} hasta que el estado sea 200 utilizando una función pollOperation debajo de createSprintEnvironment:

const pollOperation = async (operationId, intervalMs = 5000, maxAttempts = 12) => {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    await new Promise(resolve => setTimeout(resolve, intervalMs));
    const resp = await fetch(
      `${KinstaAPIUrl}/operations/${operationId}`,
      { method: 'GET', headers }
    );
    const data = await resp.json();
    if (data.status === 200) {
      console.log(`Environment ready: ${operationId}`);
      return data;
    }
    if (data.status >= 400) {
      throw new Error(`Operation failed: ${data.message}`);
    }
  }
  throw new Error('Operation timed out after maximum attempts');
};

El bucle espera cinco segundos entre cada intento y cubre hasta un minuto de tiempo de aprovisionamiento. Mientras que 200 señala la finalización, cualquier estado 4xx indica un fallo en la investigación.

El panel de control de MyKinsta muestra la lista de entornos de un sitio de WordPress, con un entorno staging en blanco, identificado por el nombre de un sprint, visible junto al entorno en producción.
El panel de MyKinsta muestra la lista de entornos de un sitio WordPress.

Si ejecutas todo esto con node app.js e inicias un sprint en Jira, el entorno debería aparecer en MyKinsta en uno o dos minutos.

Mantén a tu agencia un paso por delante con los entornos de sprint

Esta integración ofrece un entorno staging limpio y con un nombre específico dentro de MyKinsta, basado en el inicio de un sprint en Jira. Se activa el webhook, el middleware asocia el ID del tablero a un sitio, la API de Kinsta se encarga del resto y el equipo puede acceder a sus tickets en un entorno que ya les está esperando.

Cuando el middleware esté listo para entrar en producción, Sevalla es un destino de despliegue muy sencillo. Solo tienes que subir el proyecto a un proveedor de Git, conectar el repositorio, añadir las variables de entorno y actualizar la URL del webhook de Jira con la dirección de producción.

Además, el Programa para Socios de Agencias de Kinsta es ideal para las agencias que gestionan proyectos de varios clientes. Te ofrece soporte dedicado, oportunidades de marketing conjunto y el tipo de asociación de infraestructura que respalda la capa de automatización que estás construyendo sobre la API de Kinsta.

Joel Olawanle Kinsta

Joel es un desarrollador Frontend que trabaja en Kinsta como Editor Técnico. Es un formador apasionado enamorado del código abierto y ha escrito más de 200 artículos técnicos, principalmente sobre JavaScript y sus frameworks.