スプリント開始時には、タスク整理だけでなく開発環境の準備も欠かせません。2週間サイクルでクライアントのWordPress案件を進めるWeb制作会社では、最初のチケットに取りかかる前にステージング環境を用意するのが一般的です。Kinstaを利用している場合は、MyKinstaから環境を作成できます。
この作業自体は数分で終わりますが、小さなタスクだからこそ後回しにされがちです。
Kinsta APIを使えば、この手間を自動化できます。たとえば、Jiraでスプリントが開始されたタイミングでWebhookをトリガーし、ミドルウェア側でイベントを受け取るよう設定できます。そこからペイロードを読み込み、対象のKinstaサイトへ紐づけたうえでAPIを呼び出し、新しいステージング環境を自動で作成できます。
制作会社が環境のプロビジョニングを自動化すべき理由
スプリントの計画後に毎回環境を作成するとなると、MyKinstaを開き、数多くのサイト一覧から対象のクライアントサイトを探し、環境を作成して名前を付け、それからJiraに戻る必要があります。作業自体は複雑ではありませんが、すべてのクライアントプロジェクトで、毎回適切なタイミングに行わなければなりません。
もしこの作業を省略すると、チームは前回のスプリントで使っていた環境のまま作業を続けることになります。すると変更が積み重なり、不具合が発生したときには、原因の切り分けがデバッグというより“発掘作業”のようになってしまいます。
開始前に必要なもの
Kinsta APIとJiraを連携するには、少なくとも1つ以上のWordPressサイトを運用しているMyKinstaアカウント、Webhookを設定するための管理者権限を持つJira Cloudアカウント、そしてローカル環境にインストールされたNode.jsが必要になります。
Kinsta APIを利用するには、まず API キーを作成します。MyKinstaにログインし、「企業の設定」>「APIキー」に移動し、「APIキーを作成」をクリックしてください。

次にキーに名前を付けて有効期間を設定し、「生成」をクリックします。キーが表示されるのは一度のみのため、次に進む前にメモしておいてください。
この値を、Kinstaの企業IDとあわせて、プロジェクトルートにある.envファイルへ追加します。
KINSTA_API_KEY=your_api_key_here
KINSTA_COMPANY_ID=your_company_id_here
JiraとKinstaのサイトIDを取得する
https://my.kinsta.com/sites/details/fbab4927-e354-4044-b226-29ac0fbd20ca/…
Jira側では、連携したい各プロジェクトの数値ボードIDが必要です。これはURL内に表示されています(この例では2)。
https://your-domain.atlassian.net/jira/software/projects/SCRUM/boards/2
これは、Jiraがsprint_started WebhookのペイロードにoriginBoardIdとして含める値と同じものです。ボードIDとサイトIDのマッピングは、.envファイルに保存します。
board_id_client_a=2
SITE_ID_CLIENT_A=fbab4927-e354-4044-b226-29ac0fbd20ca
ボードID_CLIENT_B=5
SITE_ID_CLIENT_B=44b5a6d1-c83f-4b0e-9a1c-2e7dbc903fa1
また、ローカル開発環境では、Jiraからlocalhostへ直接アクセスすることはできません。そのため、Ngrokを使ってローカルポートを一時的なパブリックURLとして公開する必要があります。ミドルウェアをデプロイして公開URLを取得した後は、そのURLに置き換えることができます。
JiraとKinsta APIを使ってスプリント環境のプロビジョニングを自動化する方法
この連携は、JiraとKinstaの2つのシステムをまたいで動作します。Jiraでは、スプリントが開始されるとWebhookが実行され、イベントペイロードがミドルウェアに送信されます。Kinsta側では、ミドルウェアがペイロードからボードIDを読み取り、設定済みのマッピングを使ってサイトIDを特定します。そのうえでKinsta APIを呼び出し、スプリント名に基づいた標準ステージング環境を作成します。
1. スプリントイベントのためにJira webhookを登録する

そこから「Advanced」>「WebHooks」を選択し、「Create a WebHook」をクリックします。

ここでWebhook名を入力し、ミドルウェアのURLの末尾に/sprintを追加したものを貼り付けます(現時点ではダミーURLでも構いません)。続いて、「Events」で「Sprint」>「started」を選択してください。これにより、Jiraインスタンス全体でsprint_startedイベントが発生するたびに実行される管理者Webhookが作成されます。

2つ目の方法は、POST /rest/webhooks/1.0/webhookを使用するREST APIです。Webhookの登録をデプロイスクリプトに組み込みたい場合は、こちらの方法が便利です。
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
}'
{
"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"
}
}
2. ミドルウェアエンドポイントを構築する
Webhookを設定したら、次は新しいNode.jsプロジェクトを初期化し、dotenvとあわせてExpress.jsをインストールします。
npm init -y
npm install express dotenv
expressはルーティングとリクエスト処理を担当し、dotenvは.envファイルの環境変数を読み込みます。続いて、サーバーをセットアップするためにapp.jsを作成します。
// 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'));
エンドポイントのセキュリティ
Jiraは各ペイロードに署名し、そのハッシュ値をsha256=<hash>形式でX-Hub-Signatureヘッダーに含めます。Webhookシークレットは、他の認証情報とあわせて.envファイルに追加します。
JIRA_WEBHOOK_SECRET=your_webhook_secret_here
検証関数はapp.js内にあり、Node.js標準のcryptoモジュールを使用します。この関数は、リクエストヘッダーから署名を取得し、生のリクエストボディをもとに期待されるHMACを計算したうえで、timingSafeEqualを使ってタイミング攻撃を防ぎながら両者を比較します。以下は、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)
);
}
この関数は、POST /sprintルートハンドラの冒頭で呼び出されます。検証に失敗した場合、ミドルウェアは直ちに401レスポンスを返し、それ以降の処理は実行されません。
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
});
3. Kinsta APIで認証してサイト環境を取得する
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;
};
この関数は、GET /sites/{siteId}/environmentsを呼び出し、レスポンス内の最初の環境(通常は本番環境)のIDを返します。もし対象サイトで複数の環境を使用しており、特定の環境を指定したい場合は、単純に最初の結果を使うのではなく、環境名を基準に一致するものを取得するようにしてください。
4. Kinsta APIを使用してプレーンなステージング環境を作成する
サイトIDと環境IDを取得したら、ミドルウェアはPOST /sites/{siteId}/environments/plainを呼び出してスプリント用の環境を作成します。この処理は、getEnvironmentIdの下に追加するcreateSprintEnvironment関数で実装します。
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は、MyKinstaに表示される環境名です。Jiraペイロードのsprint.nameをそのまま使用することで、MyKinsta上の各環境がどのスプリントに対応しているかを判別しやすくなります。is_premiumフラグは、作成する環境を標準ステージング環境にするか、プレミアムステージング環境にするかを指定します。falseに設定すると、標準ステージング環境が作成されます。
リクエストがKinstaに受け付けられると、完成した環境そのものではなく、operation_idを含む202 Acceptedレスポンスが返されます。
{
"operation_id": "environments:add-plain-54fb80af-576c-4fdc-ba4f-b596c83f15a1",
"message": "Adding plain environment in progress",
"status": 202
}
Kinstaの非同期処理により、プロビジョニングが完了するまでリクエストスレッドがブロックされるのを防げます。operation_idは、処理の進捗を追跡するためにエンドポイントへ渡す識別子です。次に、POST /sprintルートを更新し、2つの関数を順番に呼び出すようにします。
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' });
}
});
tryブロックを使うと、複数のif文に依存するよりも処理をすっきり書けます。ただし、Jiraの署名検証は他の処理より前に実行する必要があるため、ルートハンドラの冒頭に残してください。
5. 操作ステータスをポーリングし、プロビジョニングを確認する
プロビジョニングの完了を確認するには、createSprintEnvironmentの下にpollOperation関数を追加し、GET /operations/{operation_id}をポーリングします。ステータスが200で返されるまで、処理の進捗を確認します。
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');
};

これらの処理をnode app.jsで実行し、Jiraでスプリントを開始すると、1〜2分以内にMyKinstaに新しい環境が表示されます。
この連携により、Jiraでスプリントを開始するだけで、MyKinsta内にクリーンで名前付きのプレーンなステージング環境を自動作成できます。Webhookが起動すると、ミドルウェアがボードIDから対象サイトを特定し、Kinsta APIが環境の作成を実行します。チームは、準備済みの環境ですぐに作業へ取りかかれます。
ミドルウェアの準備ができたら、Sevallaはシンプルなデプロイ先として利用できます。プロジェクトをGitサービスへプッシュし、リポジトリを接続して環境変数を設定したあと、JiraのWebhook URLを本番用のURLへ更新するだけです。
また、Kinstaのエージェンシーパートナープログラムは、複数のクライアント案件を管理する制作会社に適しています。専用サポートや共同マーケティングの機会に加え、Kinsta API上で構築する自動化ワークフローを支えるインフラパートナーシップも提供されています。