はじめに
ChatworkとDifyを連携し、「Chatwork上で話しかけるとAIが自動返信してくれるBot」を作ってみました。この記事では、Webhookを利用してChatworkからのメッセージをDifyに送り、返答をChatworkに戻す仕組みを構築した手順を詳しく紹介します。
🧠 対象読者:ChatworkでAI Botを試してみたいエンジニア・非エンジニア、DifyをWebhook連携で活用したい方
全体構成の概要
[Chatwork] → [GAS (Webhook)] → [Dify API] → [GAS] → [Chatworkに返信]
- ChatworkでBot宛にメッセージを送信
- GASがWebhookで受信し、Difyに問い合わせ
- Difyからの応答をChatworkに投稿
🔧 事前準備:Difyでチャットフローを作成し、APIキーを取得
ChatworkとDifyを連携させるには、あらかじめDify上でチャットアプリを作成し、APIキーを取得しておく必要があります。
ステップ①:Difyにログイン/登録
ステップ②:チャットアプリの作成
- 左メニューの「My Apps」から「+ Create App」をクリック。
- 「Chat App」を選び、アプリ名と簡単な説明を入力して作成します。
※この段階では、プロンプトの詳細設計などは未設定でも構いません。
ステップ③:APIキーを取得
- 左からマムのAPIアクセスアイコン > 「APIキー」クリックし、新規にAPIキーを新規作成し、控えておきます。
この「APIキー」が、後のGAS(Google Apps Script)連携の中で必要になります。
記事の冒頭にこのセクションを配置しておけば、読者がスムーズにChatwork連携へ進めます。
ステップ①:Chatwork Botの準備
- Chatwork APIトークンを発行する
https://help.chatwork.com/hc/ja/articles/115000172402-API%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%82%92%E7%99%BA%E8%A1%8C%E3%81%99%E3%82%8B - Botとして使うアカウントののアカウントIDを確認
https://help.chatwork.com/hc/ja/articles/360000142962-%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88ID%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B
ChatworkのBotアカウントで、[To:XXXXXX]
のXXXXXX
がアカウントIDです。 - Webhookを設定
ChatworkのWebhook設定画面から次を設定:
https://help.chatwork.com/hc/ja/articles/115000169541-Webhook%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B- イベントタイプ:
message_created
- Webhook URL:Google Apps Script の WebアプリURL
↑はGASをデプロイした際に発行されるURLを記載すことになります。
- イベントタイプ:
- チャットルームの設定
https://help.chatwork.com/hc/ja/articles/360000142942-%E3%83%AB%E3%83%BC%E3%83%A0ID%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B- BOT起動用のグループチャットを作成し、roomIdをメモしておく。
ステップ②:Google Apps Scriptに事前準備した変数を宣言しておく
// Chatwork連携用の設定
const CHATWORK_API_TOKEN = "ここにChatworkのAPIトークンを入力";
const BOT_ACCOUNT_ID = "100xxxxx"; // ChatworkのBot自身のアカウントID(数値で指定)
// Dify連携用の設定
const DIFY_API_KEY = "ここにDifyのAPIキーを入力"; // Difyで発行したAPIキー
ステップ③:Google Apps ScriptでWebhook受信処理を書く
doPost(e)
— ChatworkからのWebhookを受け取る関数
function doPost(e) {
const rawText = e.postData.contents;
const data = JSON.parse(rawText);
const eventType = data.webhook_event_type;
const event = data.webhook_event;
const roomId = event.room_id;
const accountId = event.account_id || event.from_account_id;
const message = event.body;
// ✅ メッセージ本文にBot宛のIDがあるか確認(部分一致)
const toTag = `${BOT_ACCOUNT_ID}`;
if (!message.includes(toTag)) {
Logger.log("Bot宛メンションが含まれていないため無視");
return ContentService.createTextOutput("ignored");
}
Logger.log(`Bot宛メッセージ受信: room=${roomId}, from=${accountId}, message=${message}`);
// 実行
handleChatworkRequest(roomId, accountId, message);
return ContentService.createTextOutput("ok");
}
一応無限ループを避けるためにメッセージ内にBotのIDがないかをチェックする挙動を追加しています。
ステップ④:Difyとの連携処理
作成済みのDifyチャットに向けて、チャットワークで投稿された質問をリクエストし、回答レスポンスを取得する
function callDifyAPI(message, conversationId, userId) {
const url = 'https://api.dify.ai/v1/chat-messages';
const headers = {
'Authorization': `Bearer ${DIFY_API_KEY}`,
'Content-Type': 'application/json'
};
const payloadObject = {
inputs: {},
query: message,
user: userId,
response_mode: 'blocking'
};
if (isValidUUID(conversationId)) {
payloadObject.conversation_id = conversationId;
}
const options = {
method: 'post',
headers,
payload: JSON.stringify(payloadObject),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
const text = response.getContentText();
Logger.log("Dify応答: " + text);
return JSON.parse(text);
}
ステップ⑤:Difyの応答をChatworkへ投稿
チャット枠で投げられたテキストをDifyのチャットに投稿AIからのレスポンスを取得し、ChatWorkのAPIにリクエストを送り投稿する。
unction postToChatwork(roomId, accountId, text) {
const url = `https://api.chatwork.com/v2/rooms/${roomId}/messages`;
const body = `[To:${accountId}] ${text}`;
const options = {
method: 'post',
headers: {
'X-ChatWorkToken': CHATWORK_API_TOKEN
},
payload: {
body: body
},
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
Logger.log("Chatwork HTTPステータス: " + response.getResponseCode());
Logger.log("Chatwork レスポンス本文: " + response.getContentText());
}
ステップ⑤:全体連携関数
状態を監視しつつ、各関数と連携し、BOTを動かします。
function handleChatworkRequest(roomId, accountId, message) {
let conversationId = getConversationId(accountId);
const result = callDifyAPI(message, conversationId, accountId);
// ❗エラー: 不存在の会話ID
if (result.code === 'not_found' && result.message === 'Conversation Not Exists.') {
clearConversationId(accountId); // 削除
Logger.log("不正なconversation_idを削除し再実行");
return handleChatworkRequest(roomId, accountId, message); // 再送信
}
// ✅ 新しい会話IDが返されたら保存
if (result.conversation_id && isValidUUID(result.conversation_id)) {
setConversationId(accountId, result.conversation_id);
}
const reply = result.answer || "申し訳ありません、うまく回答できませんでした。";
Logger.log(`【DEBUG】user=${accountId}, sentID=${conversationId}, returnedID=${result.conversation_id}`);
postToChatwork(roomId, accountId, reply);
}
ステップ⑥:会話IDの管理
このコードは、GAS (Google Apps Script) 上で「Difyとの会話を継続するための状態管理」を行うための補助関数群です。
それぞれの関数の意味を説明します。
function getConversationId(accountId) {
return PropertiesService.getScriptProperties().getProperty(accountId);
}
function setConversationId(accountId, conversationId) {
PropertiesService.getScriptProperties().setProperty(accountId, conversationId);
}
function clearConversationId(accountId) {
PropertiesService.getScriptProperties().deleteProperty(accountId);
}
function isValidUUID(str) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(str);
}
✅ 各関数の説明
1. getConversationId(accountId)
function getConversationId(accountId) {
return PropertiesService.getScriptProperties().getProperty(accountId);
}
- 目的:指定した
accountId
に紐づく「会話ID(conversation_id)」を取得します。 - 使われ方:Chatworkのユーザーがメッセージを送ってきた際、そのユーザーに対して以前の会話があるか確認するために使います。
- 内部ストレージ:GAS の「Script Properties」という簡易的なKey-Valueストレージを使用。
2. setConversationId(accountId, conversationId)
function setConversationId(accountId, conversationId) {
PropertiesService.getScriptProperties().setProperty(accountId, conversationId);
}
- 目的:新しく取得した
conversationId
を、ユーザーのaccountId
に紐づけて保存します。 - 使われ方:Dify APIから新たな会話IDが返ってきたときに保存する。
3. clearConversationId(accountId)
function clearConversationId(accountId) {
PropertiesService.getScriptProperties().deleteProperty(accountId);
}
- 目的:該当ユーザーとの会話IDを削除します(例:Dify APIが「この会話IDは存在しません」と返した場合などに使う)。
- 使われ方:壊れたセッションをリセットして、新しい会話から再開させるため。
4. isValidUUID(str)
function isValidUUID(str) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(str);
}
- 目的:文字列が正しい形式の UUID(Difyのconversation_id)かどうかを検証します。
- 使われ方:Dify APIのレスポンスに含まれるIDが有効な形式かチェックするため。
🔄 全体の流れの中での役割
この4つは、DifyとChatworkのBot連携において「会話の文脈を維持する」ために必要です。Difyは、conversation_id
を使うことで、前回までの会話の履歴を保持したままチャットできます。これらの関数がなければ、毎回会話がリセットされ、文脈を無視した応答しかできなくなります。
まとめ:Chatworkで「AIと自然に会話できるBot」の実現
この構成により、Chatwork上に「Difyを通じて会話できるAIチャットボット」が構築できました。以下のような特徴があります:
- ✅ Chatworkに話しかけると即座にAIが返事
- ✅ 会話の文脈を保持(Difyのconversation_idで継続)
- ✅ GASだけで構築でき、無料運用も可能
今後の応用例
- 社内FAQボット
- 問い合わせ自動対応
- プロジェクト管理Botとしての活用
ご質問や改善アイデアがあれば、ぜひコメント・フィードバックください!
今後もDify × Chatworkの活用ノウハウを発信していきます。
以下に全スクリプトを一つなぎで記載しておきます。
必要なIDなどは変更の上活用ください。
// 設定:各自のトークンとAPIキーを入れてください
const DIFY_API_KEY = "app-HOGEHOGRHOGRHOGR";
const BOT_ACCOUNT_ID = "10000000";
const CHATWORK_API_TOKEN = 'fugafugafugafugafugafuga';
function getConversationId(accountId) {
return PropertiesService.getScriptProperties().getProperty(accountId);
}
function setConversationId(accountId, conversationId) {
PropertiesService.getScriptProperties().setProperty(accountId, conversationId);
}
function clearConversationId(accountId) {
PropertiesService.getScriptProperties().deleteProperty(accountId);
}
function isValidUUID(str) {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(str);
}
function callDifyAPI(message, conversationId, userId) {
const url = 'https://api.dify.ai/v1/chat-messages';
const headers = {
'Authorization': `Bearer ${DIFY_API_KEY}`,
'Content-Type': 'application/json'
};
const payloadObject = {
inputs: {},
query: message,
user: userId,
response_mode: 'blocking'
};
if (isValidUUID(conversationId)) {
payloadObject.conversation_id = conversationId;
}
const options = {
method: 'post',
headers,
payload: JSON.stringify(payloadObject),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
const text = response.getContentText();
Logger.log("Dify応答: " + text);
return JSON.parse(text);
}
function postToChatwork(roomId, accountId, text) {
const url = `https://api.chatwork.com/v2/rooms/${roomId}/messages`;
const body = `[To:${accountId}] ${text}`;
const options = {
method: 'post',
headers: {
'X-ChatWorkToken': CHATWORK_API_TOKEN
},
payload: {
body: body
},
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
Logger.log("Chatwork HTTPステータス: " + response.getResponseCode());
Logger.log("Chatwork レスポンス本文: " + response.getContentText());
}
function handleChatworkRequest(roomId, accountId, message) {
let conversationId = getConversationId(accountId);
const result = callDifyAPI(message, conversationId, accountId);
// ❗エラー: 不存在の会話ID
if (result.code === 'not_found' && result.message === 'Conversation Not Exists.') {
clearConversationId(accountId); // 削除
Logger.log("不正なconversation_idを削除し再実行");
return handleChatworkRequest(roomId, accountId, message); // 再送信
}
// ✅ 新しい会話IDが返されたら保存
if (result.conversation_id && isValidUUID(result.conversation_id)) {
setConversationId(accountId, result.conversation_id);
}
const reply = result.answer || "申し訳ありません、うまく回答できませんでした。";
Logger.log(`【DEBUG】user=${accountId}, sentID=${conversationId}, returnedID=${result.conversation_id}`);
postToChatwork(roomId, accountId, reply);
}
function doPost(e) {
const rawText = e.postData.contents;
const data = JSON.parse(rawText);
const eventType = data.webhook_event_type;
const event = data.webhook_event;
const roomId = event.room_id;
const accountId = event.account_id || event.from_account_id;
const message = event.body;
// ✅ メッセージ本文にBot宛のメンションがあるか確認(部分一致)
const toTag = `${BOT_ACCOUNT_ID}`;
if (!message.includes(toTag)) {
Logger.log("Bot宛メンションが含まれていないため無視");
return ContentService.createTextOutput("ignored");
}
Logger.log(`Bot宛メッセージ受信: room=${roomId}, from=${accountId}, message=${message}`);
// 実行
handleChatworkRequest(roomId, accountId, message);
return ContentService.createTextOutput("ok");
}