Aikido Webhooks
Webhookの認証
Aikidoから受信したWebhookが実際にAikidoから送信されたものであり、そのペイロードが改ざんされていないことを検証するために、Aikidoはお客様と共有するシークレットで署名したペイロードのハッシュを使用します。
Aikidoは常に次のIPアドレスからWebhookリクエストを送信します: 52.18.113.172
Webhookスキーマ
WebhookスキーマはAPIドキュメントで確認できます。
Webhookシークレットの生成
-
Webhook連携ページに移動します
-
テーブルの右上にある「Add secret」をクリックしてシークレットを作成します。

-
Webhookシークレットが作成されると、モーダルが表示されます。ここでWebhookシークレットをコピーし、安全に保管してください。このシークレットは安全に保管し、どのコードリポジトリにもコミットしないことが重要です
Webhookの検証
設定したイベントについてAikidoがWebhookを送信する際には、イベントのペイロードのハッシュを作成し、先ほど生成したシークレットで署名します。この一意のハッシュはX-Aikido-Webhook-Signatureリクエストヘッダーに含まれます。これにより、Webhookとそのペイロードが本物であることを検証できます。
リプレイ攻撃を防ぐため、HTTPリクエストを送信する直前のエポックタイムスタンプをWebhookペイロードに含めています。このタイムスタンプはdispatched_atプロパティとして含まれ、ペイロードを検証する時点で30秒より古くないことを確認する必要があります。
どのプログラミング言語を選択しても、受信したWebhookを検証する手順は次のとおりです:
- ペイロードが有効なJSON文字列であることを確認します
X-Aikido-Webhook-Signatureリクエストヘッダーから署名を取得します- リクエストボディをJSON文字列に変換し直します
- 文字列化したリクエストボディから
sha256アルゴリズムでHMACダイジェストを作成し、AikidoのWebhookシークレットで署名します - リクエストヘッダーの署名が、生成したダイジェストと一致することを検証します。
- プロパティの
dispatched_atエポックタイムスタンプが30秒より古くないことを検証します
以下は、expressフレームワークを使用する場合にハッシュを検証する方法を示すJavaScriptの疑似コードです。これはミドルウェアとして組み込み、値が有効かどうかを確認するために追加の検証を行う必要があります。
const { createHmac } = require('node:crypto');
const express = require('express');
const bodyParser = require('body-parser');
const PORT = 4000;
const app = express();
app.use(bodyParser.json());
const isIncomingWebhookValid = (payload, signature) => {
// get the raw webhook secret from the environment variables
const aikidoWebhookSecret = process.env.AIKIDO_WEBHOOK_SECRET;
// lets create the hmac instance
const hmac = createHmac('sha256', aikidoWebhookSecret);
// lets convert the payload object to a JSON string
const rawPayload = JSON.stringify(payload);
// update the hmac content with the stringified payload
hmac.update(rawPayload);
// calculate the digest of the hmac content and return it as a hex value
const payloadDigest = hmac.digest('hex');
// if digest does not match the provided signature, the webhook is invalid
if (payloadDigest !== signature) return false;
// get the current epoch timestamp
const currentEpochTimestamp = Math.floor(new Date().getTime() / 1000);
// if the dispatched_at epoch timestamp from the payload is longer than 30 seconds old, the webhook is invalid
if ((currentEpochTimestamp - (payload['dispatched_at'] ?? 0)) > 30) return false;
// webhook passed all checks and is valid
return true;
}
app.post('/webhooks/aikido/issue-created', async (req, res) => {
const isValid = isIncomingWebhookValid(req.body, req.headers['X-Aikido-Webhook-Signature']);
if (!isValid) {
throw new Error(`The request signature is invalid`)
}
// your business code
res.status(204);
});
app.listen(PORT, () => {
console.log(`server listening on port ${port}`);
});