Aikido Webhooks
Webhookの認証
Section titled “Webhookの認証 ”Aikido から届く受信 Webhook が実際に Aikido から送信されたものであり、そのペイロードが改ざんされていないことを検証するために、Aikido はあなたと共有したシークレットで署名されたペイロードのハッシュを使用します。
Webhookのスキーマ
Section titled “Webhookのスキーマ ”Webhook のスキーマはこちらの API ドキュメントで確認できます。
Webhookシークレットの生成
Section titled “Webhookシークレットの生成 ”-
webhooks integration ページに移動します
-
右側のテーブルのすぐ上にある「Add secret」をクリックしてシークレットを作成します。

-
Webhook シークレットが作成されると、モーダルが表示され、Webhook シークレットをコピーして安全に保存できます。このシークレットは安全に保管し、コードリポジトリにコミットしないことが重要です
Webhookの検証
Section titled “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}`);});