Ogni sprint inizia con una bacheca piena di ticket e un team che ha bisogno di un posto vuoto su cui lavorare. Per le agenzie che gestiscono progetti WordPress per clienti con cicli di due settimane, ciò significa creare un ambiente di staging in MyKinsta prima che venga preso in carico il primo ticket.

Si tratta di un’operazione che richiede pochi minuti, ma è il tipo di compito che passa inosservato perché sembra davvero banale.

L’API di Kinsta può eliminare questo passaggio. Quando inizia uno sprint in Jira, puoi impostare un webhook che attiva un evento nel middleware, il quale legge il payload, lo mappa in un sito Kinsta e chiama l’API per creare un nuovo ambiente di staging.

Perché le agenzie dovrebbero automatizzare il provisioning degli ambienti

Creare un ambiente dopo aver pianificato uno sprint significa aprire MyKinsta, trovare il sito del cliente giusto da un elenco che ne contiene decine, creare e dare un nome all’ambiente e poi tornare a Jira. Sebbene non sia un’operazione complicata, deve avvenire al momento giusto, ogni volta e per ogni progetto in corso.

Se si salta questa operazione, il team inizia a lavorare nell’ambiente dell’ultimo sprint. Da lì, le modifiche si accumulano l’una sull’altra e quando si verifica un bug, isolarlo è più simile a cercare un ago in un pagliaio che a fare debugging.

Cosa serve prima di iniziare

Per collegare l’API di Kinsta e Jira, serve un account Kinsta con almeno un sito WordPress in un ambiente esistente, un account Jira Cloud con accesso da amministratore per configurare i webhook e Node.js installato in locale.

Per autenticarti con l’API di Kinsta, vai su [La tua azienda] > Impostazioni azienda > Chiavi API in MyKinsta e clicca su Crea chiave API.

La dashboard di MyKinsta mostra la schermata delle chiavi API, che include due chiavi API esistenti, oltre a un pulsante per crearne una nuova.
La dashboard di MyKinsta mostra le chiavi API.

Quindi, assegna un nome alla chiave, imposta una durata di scadenza e clicca su Genera. La chiave viene visualizzata una sola volta, quindi annotala prima di andare avanti.

Inseriscila poi in un file .env nella root del progetto insieme all’ID della tua azienda Kinsta, che puoi trovare in Impostazioni azienda > Dettagli di fatturazione:

KINSTA_API_KEY=your_api_key_here
KINSTA_COMPANY_ID=your_company_id_here

Ottenere gli ID dei siti Jira e Kinsta

Hai bisogno dell’ID del sito Kinsta per ogni progetto cliente dell’automazione. Si tratta di un UUID che Kinsta assegna al momento della creazione del sito. Viene visualizzato nell’URL MyKinsta quando apri un sito o tramite il polling della chiamata GET /sites una volta che la chiave API è attiva:

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

Per quanto riguarda Jira, hai bisogno dell’ID numerico della scheda per ogni progetto che vuoi collegare. Viene visualizzato nell’URL (qui come 2):

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

Questo è lo stesso valore che Jira include nel payload del webhook sprint_started come originBoardId. La mappatura degli ID delle schede e degli ID dei siti si trova nel tuo file .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

Inoltre, per lo sviluppo locale, Jira non può raggiungere direttamente localhost. Ngrok può essere utilizzato per esporre una porta locale a internet con un URL pubblico temporaneo, che puoi utilizzare come endpoint del webhook durante lo sviluppo. Quando avrai un indirizzo middleware distribuito, potrai sostituirlo.

Come automatizzare il provisioning dell’ambiente di sprint con Jira e l’API di Kinsta

Questa integrazione si svolge su due sistemi. In Jira, un webhook si attiva quando inizia uno sprint e consegna il payload dell’evento al middleware. Per Kinsta, il middleware legge l’ID della scheda dal payload, lo risolve in un ID del sito utilizzando la mappa di configurazione e chiama l’API di Kinsta per creare un ambiente di staging semplice con il nome dello sprint.

1. Registrare un webhook Jira per gli eventi dello sprint

Jira Cloud offre due modi per registrare un webhook. L’opzione più semplice per la maggior parte dei team è attraverso l’interfaccia utente di Jira. L’opzione Settings > System si trova nel menu in alto a destra:

L'opzione 'Jira Cloud System' nel menu delle impostazioni generali dell'interfaccia utente di Jira.
L’opzione System di Jira Cloud nelle impostazioni generali.

Da qui, scegli Advanced > WebHooks, quindi clicca su Create a WebHook:

Il pannello di amministrazione di Jira Cloud che mostra la sezione WebHooks con il pulsante 'Create un WebHook' in alto a destra.
Sezione WebHooks con il pulsante Create a WebHook in alto a destra.

Qui inserisci un nome, incolla un URL middleware con l’aggiunta di /sprint (per ora va bene un’opzione fittizia) e alla voce Events seleziona Sprint > started. In questo modo viene creato un webhook amministrativo che si attiva per ogni evento sprint_started in tutta la tua istanza Jira.

Il modulo di creazione dei webhook di Jira che mostra il campo 'Name', il campo 'URL' e la sezione 'Eventi' con l'opzione 'Sprint started' selezionata.
Il modulo di creazione del webhook di Jira.

La seconda opzione è quella di usare l’API REST, utilizzando POST /rest/webhooks/1.0/webhook. Questa opzione funziona bene quando la registrazione dei webhook fa parte di uno script di distribuzione:

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
  }'

Chiamando PUT /rest/webhooks/1.0/webhook/refresh si aggiunge un’estensione al tempo di scadenza di 30 giorni per il webhook. Quando Jira lancia sprint_started, il payload arriva al tuo endpoint come un JSON POST con la seguente struttura:

{
  "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"
  }
}

Il middleware utilizza sprint.originBoardId per cercare l’ID del sito Kinsta e sprint.name per nominare il nuovo ambiente: ogni evento sprint_started all’interno della tua istanza Jira raggiunge l’endpoint. La ricerca dell’ID del sito nella mappa di configurazione è l’elemento che consente all’automazione di essere indirizzata al progetto client giusto e di ignorare tutto il resto.

2. Crea l’endpoint middleware

Una volta creato il webhook, devi inizializzare un nuovo progetto Node.js e installare Express.js insieme a dotenv:

npm init -y
npm install express dotenv

express gestisce il routing e l’analisi delle richieste, mentre dotenv carica il tuo file .env. Devi creare il file app.js per configurare il server. Ecco il file 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'));

Per proteggere l’endpoint, genera una chiave segreta durante la configurazione del webhook. Jira la rende facoltativa durante la configurazione, ma è praticamente essenziale per un’istanza sicura.

Sicurezza dell’endpoint

Jira firma ogni payload e include il risultato nell’header X-Hub-Signature come sha256=<hash>. Il segreto va aggiunto al file .env insieme alle altre credenziali:

JIRA_WEBHOOK_SECRET=your_webhook_secret_here

La funzione di verifica si trova in app.js e utilizza il modulo crypto integrato di Node. Legge la firma dall’header della richiesta, calcola l’HMAC previsto rispetto al corpo della richiesta grezza e utilizza timingSafeEqual per confrontarli in modo da evitare attacchi di tipo timing. Ecco la parte rilevante di 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)
  );
}

Questa funzione è la prima cosa che viene chiamata all’interno del gestore del percorso POST /sprint. Se la verifica fallisce, il middleware restituisce immediatamente 401 e non viene eseguito nient’altro:

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 route utilizza express.raw() sul percorso /sprint perché verifyJiraSignature ne ha bisogno per calcolare l’HMAC. Una volta superata la verifica, JSON.parse(req.body) fornisce lo stesso risultato di express.json().

3. Autenticazione con l’API di Kinsta e recupero degli ambienti del sito

Tutte le richieste all’API di Kinsta utilizzano l’autenticazione con token Bearer: la costante headers in app.js gestisce questo aspetto per ogni richiesta dell’applicazione. La riga require('dotenv').config() in alto assicura che la chiave venga caricata da .env prima di qualsiasi altra cosa, quindi non appare mai nel codice sorgente.

Kinsta utilizza gli ID dell’ambiente piuttosto che quelli del sito per l’endpoint di provisioning, quindi devi aggiungere una funzione getEnvironmentId sotto la costante 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;
};

Questo metodo chiama GET /sites/{siteId}/environments e restituisce l’ID del primo ambiente (cioè quello attivo) nella risposta. Se un sito utilizza più ambienti e hai bisogno di puntare a uno specifico, fai una corrispondenza con il nome dell’ambiente piuttosto che prendere il primo risultato.

4. Creare un ambiente di staging semplice utilizzando l’API di Kinsta

Una volta risolti gli ID del sito e dell’ambiente, il middleware chiama POST /sites/{siteId}/environments/plain per creare l’ambiente di sprint. Questo avviene tramite la funzione createSprintEnvironment che si trova sotto 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 appare in MyKinsta, mentre utilizzando direttamente sprint.name dal payload di Jira, ogni ambiente della dashboard corrisponde allo sprint a cui appartiene. Il flag is_premium determina se Kinsta lo considera un ambiente di staging standard o premium. Impostandolo a false si crea un ambiente standard.

Quando la richiesta raggiunge Kinsta, restituisce 202 Accepted con un operation_id piuttosto che un ambiente completato:

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

L’elaborazione async di Kinsta evita un thread di richiesta bloccato mentre il provisioning viene completato. L’operation_id è ciò che passerai all’endpoint per monitorare i progressi. Quindi, aggiorna la route POST /sprint per chiamare entrambe le funzioni in sequenza:

(!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' });
  }
});

L’utilizzo del blocco try è più pulito rispetto all’utilizzo di più istruzioni if. Tuttavia, tieni la verifica della firma di Jira all’inizio del file perché deve essere eseguita prima di qualsiasi altro codice.

5. Eseguire il polling dello stato dell’operazione e confermare il provisioning

Per monitorare il completamento dell’operazione, esegui il polling di GET /operations/{operation_id} finché lo stato non viene restituito come 200 utilizzando la funzione pollOperation sotto 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');
};

Il ciclo attende cinque secondi tra ogni tentativo e copre fino a un minuto di tempo di provisioning. Mentre 200 segnala il completamento dell’operazione, qualsiasi stato 4xx indica un errore nell’elaborazione della richiesta.

La dashboard di MyKinsta che mostra l'elenco degli ambienti di un sito WordPress, con un ambiente di staging denominato
La dashboard di MyKinsta mostra l’elenco degli ambienti di un sito WordPress.

Se esegui tutto questo con node app.js e avvii uno sprint in Jira, l’ambiente dovrebbe apparire in MyKinsta entro un minuto o due.

Mantieni la tua agenzia al passo con gli sprint

Questa integrazione prevede un ambiente di staging semplice, pulito e nominato all’interno di MyKinsta, basato sull’avvio di uno sprint in Jira. Il webhook si attiva, il middleware risolve l’ID della scheda in un sito, l’API di Kinsta gestisce il resto e il team prende i suoi ticket con un ambiente pronto che li sta già aspettando.

Quando il middleware è pronto per essere operativo, Sevalla può rappresentare un’opzione di distribuzione molto semplice. Si invia il progetto a un provider Git, si collega il repo, si aggiungono le variabili d’ambiente e si aggiorna l’URL del webhook di Jira all’indirizzo live.

Inoltre, il Programma per Agenzie Partner di Kinsta è ideale per le agenzie che gestiscono più progetti di clienti. Offre un’assistenza dedicata, opportunità di co-marketing e un tipo di partnership infrastrutturale che supporta il livello di automazione che stai costruendo sull’API di Kinsta.

Joel Olawanle Kinsta

Joel è uno Frontend developer che lavora in Kinsta come redattore tecnico. È un insegnante appassionato che ama l'open source e ha scritto oltre 200 articoli tecnici principalmente su JavaScript e i suoi framework.