ChatworkにDifyを使ってAIチャットボットを作ってみた|Webhook連携で対話実現

目次

はじめに

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の準備

  1. 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
  2. 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です。
  3. 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を記載すことになります。
  4. チャットルームの設定
    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");
}
目次