コンテンツにスキップ

Webhookを活用して外部サービスと連携する

概要

Craft Cross CMSでは、コンテンツ操作に関するイベントをトリガーに、Craft Functionsで任意のバックエンド処理を実行できます。

この仕組みを使うことで、CMSのイベントを起点に外部サービスへHTTPリクエスト(Webhook)を送信し、さまざまな処理を自動化できます。たとえば次のようなユースケースがあります。

  • コンテンツの公開・非公開をトリガーにWebサイトのデプロイを実行する
  • コンテンツの公開をSlackチャンネルに通知する
  • コンテンツの公開・非公開をトリガーにGitHub Actionsのワークフローを実行する

このドキュメントでは、Hook v2とCraft Functionsを使って、CMSのイベントをトリガーにWebhookを送信する方法をユースケースごとに紹介します。

全体の流れ

CMSのWebhook連携は、次の流れで動作します。

  1. Craft Cross CMSの管理画面でコンテンツを操作する
  2. Hook v2のトリガーによって、Craft Functionsのファンクションが実行される
  3. ファンクション内で外部サービスのURLにHTTPリクエストを送信する

事前準備

各ユースケースに共通して、次の準備が必要です。

1. API v2アプリを作成する

Hook v2を利用するために、API v2アプリを作成します。

  • KARTE管理画面にログインし、[ストア > API v2設定]を選択します
  • 右上の「作成」ボタンを押します
  • 次の設定でアプリを作成します
    • タイプ: token
    • 名前:(適当な名前を付けます)
    • スコープ: beta.cms.content.get
  • 保存時に表示されるモーダルで、[Craft Secretへのトークン保存 > 保存]ボタンを押します(キー名は KARTE_APP_TOKEN など、わかりやすい名前を付けてください)

2. Craft Functionsのファンクションを作成する

Webhook送信処理を実装するファンクションを作成します。

  • KARTE管理画面の[Craft > ファンクション > 新規作成 > コードを書いて作成]を選択します
  • ファンクションのタイプを イベント駆動タイプ にします
  • コードを記述してデプロイします(各ユースケースのサンプルコードを参照)

3. Hook v2のトリガーを設定する

作成したAPI v2アプリにHook v2のトリガーを設定し、CMSのイベント発生時にファンクションが実行されるようにします。

  • 作成したAPI v2アプリの編集画面を開きます
  • [Hook設定]タブで「編集」を選択します
  • [トリガー設定]から、ユースケースに応じたCMSのイベントを選択して追加します
  • [チャンネル設定 > Craft > Hookステータス > 有効にする]にチェックを付け、手順2で作成したファンクションを選択します
  • アプリ設定を保存します

Craft Functionsが受け取るデータ

Hook v2経由でCMSイベントをトリガーにファンクションが実行された場合、data には次のような情報が含まれます。

  • data.kind: "karte/apiv2-hook"
  • data.jsonPayload.event_type: CMSイベントの種類(例: "cms/content/publish"
  • data.jsonPayload.data.id: コンテンツID
  • data.jsonPayload.data.sys.modelId: モデルID

これらの情報を使って、対象のイベントやモデルを絞り込んだ上で外部サービスへのWebhookを実行できます。

ユースケース1: Webサイトをデプロイする

コンテンツの公開・非公開をトリガーにWebサイトのデプロイを実行するケースです。VercelやNetlifyなどのホスティングサービスが提供するDeploy Hook URLにリクエストを送ることで、サイトのデプロイを自動的に開始できます。

処理の流れ

  1. CMSでコンテンツを公開・非公開にする
  2. Hook v2のトリガー(KARTE CMS: コンテンツの公開時KARTE CMS: コンテンツの非公開時)によってCraft Functionsが実行される
  3. ホスティングサービスのDeploy Hook URLにPOSTリクエストを送信する
  4. サイトのデプロイが開始される

サンプルコード

Deploy Hook URLはファンクションの[変数]タブで設定します。

const LOG_LEVEL = "<% LOG_LEVEL %>";
const DEPLOY_HOOK_URL = "<% DEPLOY_HOOK_URL %>";
export default async function (data, { MODULES }) {
const { initLogger } = MODULES;
const logger = initLogger({ logLevel: LOG_LEVEL });
// Hook v2からのトリガーでない場合はスキップ
if (data.kind !== "karte/apiv2-hook") {
return;
}
try {
const response = await fetch(DEPLOY_HOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({}),
});
if (!response.ok) {
const text = await response.text();
throw new Error(`Deploy hook request failed: ${response.status} ${text}`);
}
logger.log("Deploy hook triggered successfully.");
} catch (e) {
logger.error(`Error triggering deploy hook: ${e.message}`);
}
}

変数の設定

ファンクションの[変数]タブで次の値を設定します。

変数名説明
LOG_LEVEL出力するログのレベルDEBUG, INFO, WARN, ERROR
DEPLOY_HOOK_URLホスティングサービスのDeploy Hook URLhttps://api.vercel.com/v1/integrations/deploy/xxxx

ユースケース2: Slack通知

特定のモデル(例: 投稿)のコンテンツが公開されたときにSlackチャンネルへ通知するケースです。Slackアプリを作成し、chat.postMessage APIを使うことで通知を実現できます。

処理の流れ

  1. CMSでコンテンツを公開する
  2. Hook v2のトリガー(KARTE CMS: コンテンツの公開時)によってCraft Functionsが実行される
  3. 対象のモデルかどうかを判定する
  4. コンテンツの情報からSlackメッセージを組み立てる
  5. Slackの chat.postMessage APIでメッセージを送信する

事前準備

Slackアプリの設定

  1. Slackアプリ管理画面から[Create New App]をクリックします
  2. [From scratch]を選択し、アプリ名と対象のワークスペースを選択して作成します
  3. 作成後の画面で[OAuth & Permissions]を開き、[Bot Token Scopes]に chat:write を追加して保存します
  4. [Install to Workspace]をクリックし、認証画面で[許可する]を選択してワークスペースにインストールします
  5. 生成されたBot User OAuth Token(xoxb- で始まるトークン)を控えておきます
  6. 取得したBot User OAuth TokenをCraft Secret Managerに登録します
  7. 通知先のSlackチャンネルの設定から[インテグレーション > アプリを追加する]で作成したアプリを追加します

コンテンツ取得用のアクセストークンを確認する

コンテンツの詳細情報を取得するために、事前準備のステップ1で作成したAPI v2アプリのアクセストークンを使用します。このアプリにはスコープ beta.cms.content.get が設定されているため、Management APIでコンテンツを取得できます。

Craft Secretに保存したアクセストークンのキー名を控えておいてください。

サンプルコード

SlackのBot TokenとAPI v2アプリのアクセストークンはCraft Secret Managerから取得します。シークレット名は環境ごとに分けて管理できるよう、Craft Functionsの変数で設定しています。

モジュールの設定

ファンクションの[モジュール]タブに次の内容を設定します。

{
"api": "6.1.3",
"@slack/web-api": "7.14.1"
}

コード

コードは次のように記載します。

import api from "api";
import { WebClient } from "@slack/web-api";
const LOG_LEVEL = "<% LOG_LEVEL %>";
const TARGET_MODEL_ID = "<% TARGET_MODEL_ID %>";
const SLACK_CHANNEL_ID = "<% SLACK_CHANNEL_ID %>";
const SLACK_BOT_TOKEN_SECRET_NAME = "<% SLACK_BOT_TOKEN_SECRET_NAME %>";
const KARTE_APP_TOKEN_SECRET_NAME = "<% KARTE_APP_TOKEN_SECRET_NAME %>";
// CMS APIクライアントを初期化
const cmsClient = api("@dev-karte/v1.0#jj0g1jm98bme78");
export default async function (data, { MODULES }) {
const { initLogger, secret } = MODULES;
const logger = initLogger({ logLevel: LOG_LEVEL });
// Hook v2からのトリガーでない場合はスキップ
if (data.kind !== "karte/apiv2-hook") {
return;
}
const modelId = data.jsonPayload.data.sys.modelId;
const contentId = data.jsonPayload.data.id;
// 対象のモデル以外はスキップ
if (modelId !== TARGET_MODEL_ID) {
return;
}
try {
// Craft Secret Managerからシークレットを取得
const {
[SLACK_BOT_TOKEN_SECRET_NAME]: slackBotToken,
[KARTE_APP_TOKEN_SECRET_NAME]: token,
} = await secret.get({
keys: [SLACK_BOT_TOKEN_SECRET_NAME, KARTE_APP_TOKEN_SECRET_NAME],
});
// Management APIでコンテンツのタイトルを取得
cmsClient.auth(token);
const cmsResponse = await cmsClient.postV2betaCmsContentGet({
modelId,
contentId,
});
const content = cmsResponse.data;
const title = content.title || "(タイトルなし)";
// Slack WebClientでメッセージを送信
const slackClient = new WebClient(slackBotToken);
await slackClient.chat.postMessage({
channel: SLACK_CHANNEL_ID,
text: `CMSコンテンツが公開されました: ${title}`,
});
logger.log("Slack notification sent successfully.");
} catch (e) {
logger.error(`Error sending Slack notification: ${e.message}`);
}
}

変数の設定

ファンクションの[変数]タブで次の値を設定します。

変数名説明
LOG_LEVEL出力するログのレベルDEBUG, INFO, WARN, ERROR
TARGET_MODEL_ID通知対象のモデルIDa1234567890b2345678901c3
SLACK_CHANNEL_ID通知先のSlackチャンネルIDC01ABCDEFGH
SLACK_BOT_TOKEN_SECRET_NAME事前準備で登録したSlack Bot TokenのCraft Secret Managerのシークレット名SLACK_BOT_TOKEN
KARTE_APP_TOKEN_SECRET_NAMEAPI v2アプリのアクセストークンを保存したCraft Secret Managerのシークレット名KARTE_APP_TOKEN

送信されるメッセージ

上記のサンプルコードでは、Slackに次のようなテキストメッセージが送信されます。

CMSコンテンツが公開されました: 記事のタイトル

よりリッチなメッセージを送信したい場合は、Slack Block Kit を参照してください。

ユースケース3: GitHub Actionsを実行する

コンテンツの公開・非公開をトリガーにGitHub Actionsのワークフローを実行するケースです。たとえば、CMSのイベントを起点に静的サイトのビルドやテストを実行できます。

処理の流れ

  1. CMSでコンテンツを公開・非公開にする
  2. Hook v2を経由してCraft Functionsが実行される
  3. GitHub APIのCreate a repository dispatch eventを呼び出す
  4. 指定したGitHub Actionsワークフローが実行される

事前準備: GitHubの設定

  1. GitHub Personal Access Token(fine-grained)を作成します
    • リポジトリへのContentsRead and write権限が必要です
  2. 取得したトークンをCraft Secret Managerに登録します
  3. トリガーしたいワークフローファイル(例: .github/workflows/deploy.yml)に repository_dispatch トリガーを追加します
# .github/workflows/deploy.yml の例
name: Deploy
on:
repository_dispatch:
types: [cms-deploy] # 任意の名前を指定できます。サンプルコードの event_type と一致させてください。

サンプルコード

GitHub TokenはCraft Secret Managerから取得します。シークレット名は環境ごとに分けて管理できるよう、Craft Functionsの変数で設定しています。

const LOG_LEVEL = "<% LOG_LEVEL %>";
const GITHUB_OWNER = "<% GITHUB_OWNER %>";
const GITHUB_REPO = "<% GITHUB_REPO %>";
const GITHUB_TOKEN_SECRET_NAME = "<% GITHUB_TOKEN_SECRET_NAME %>";
export default async function (data, { MODULES }) {
const { initLogger, secret } = MODULES;
const logger = initLogger({ logLevel: LOG_LEVEL });
// Hook v2からのトリガーでない場合はスキップ
if (data.kind !== "karte/apiv2-hook") {
return;
}
try {
// Craft Secret Managerからシークレットを取得
const { [GITHUB_TOKEN_SECRET_NAME]: githubToken } = await secret.get({
keys: [GITHUB_TOKEN_SECRET_NAME],
});
// GitHub Actions の repository_dispatch イベントを発火する
const url = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/dispatches`;
const response = await fetch(url, {
method: "POST",
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${githubToken}`,
"X-GitHub-Api-Version": "2022-11-28",
},
body: JSON.stringify({
event_type: "cms-deploy",
}),
});
if (!response.ok) {
const errorBody = await response.text();
throw new Error(
`GitHub API request failed: ${response.status} ${errorBody}`,
);
}
logger.log("GitHub Actions workflow triggered successfully.");
} catch (e) {
logger.error(`Error triggering GitHub Actions: ${e.message}`);
}
}

変数の設定

ファンクションの[変数]タブで次の値を設定します。

変数名説明
LOG_LEVEL出力するログのレベルDEBUG, INFO, WARN, ERROR
GITHUB_OWNERGitHubリポジトリのオーナー名my-org
GITHUB_REPOGitHubリポジトリ名my-website
GITHUB_TOKEN_SECRET_NAME事前準備で登録したGitHub TokenのCraft Secret Managerのシークレット名GITHUB_TOKEN