← 全記事
ケーススタディ

たった1つのLINE webhookでチーム全体を動かす:福岡のスタートアップがLINEメッセージをSlack・CRM・AIに自動振り分けした事例

火曜日の朝9時47分、大阪のある顧客が担当営業のLINEにメッセージを送った。「CSVエクスポートが何度も失敗するんだけど、これって既知の問題?」担当者は会議中。そのメッセージは3時間もの間、彼女のスマホに放置されたままだった。返信した頃には、顧客はすでにウェブサイトのサポート窓口からチケットを起票し、X(旧Twitter)でも「この会社のLINE、ちゃんと有人対応しているんですか?」とタグ付けしていた。

これはプラットフォームの機能不足の話ではない。LINE自体はメッセージングアプリとして十分に優秀だ。問題の本質は、福岡オフィスの6人のメンバーがそれぞれ個人のLINEアカウントで顧客メッセージを受け取り、誰が何に対応しているかの共有ビューが一切なかったことにある。この会社――日本および東南アジアの倉庫向けに物流計画ツールを販売するB2B SaaSチーム――は、「各自が自分のLINEを確認すればいい」というサポート体制が通用する段階をとっくに過ぎていた。しかし、どの代替案を検討しても、顧客にLINE以外のプラットフォームへ移行してもらう必要がありそうだった。日本国内でLINE離れを顧客に求めるのは、現実的ではない。

存在しなかった「キュー」

2026年初頭時点でのこのチームのサポート体制はこうだった。営業担当3名、サポートエンジニア2名がそれぞれ顧客から友だち追加されたLINEアカウントを持ち、メッセージは各人のスマホに届く。共有受信箱も、対応履歴の追跡も、SLAもない。誰かが病欠すれば、その人のメッセージは復帰するまで未読のまま。午後7時以降に顧客からメッセージが来れば、翌営業日まで待たせる――対応する気がないのではなく、メッセージが届いたことに誰も気づかないのだ。

このチームもLINE公式アカウント(OA)の導入を検討したことはあった。OAなら共有受信箱といくつかの自動化機能が使える。しかし、OAの認証手続きには最大60営業日かかるうえ、未認証ティアでは友だち数の上限が500人――顧客ベースが800人に迫るこのチームにはハードルが高すぎた。認証済みティアやプレミアムティアなら上限は緩和されるが、月額費用と、事業者側からいつ連絡できるかを規定するインタラクションウィンドウの制約が付いて回る。このチームは自らプッシュ発信するのではなく、顧客からの問い合わせに対応する側だった。OAの一斉配信機能は、このチームが抱えていない課題を解決するものだったのだ。

一方で、現状維持のオペレーションコストはじわじわと積み上がっていた。2026年第1四半期だけで、顧客メッセージが4時間以上放置されたケースが23件記録された。そのうち7件は他チャネルからのエスカレーションに発展し、2件はXでの公開クレームにつながった。サポートリードの健二は未対応メッセージを追跡するスプレッドシートを作り始めたが、6週目で更新をやめてしまった。

1つのwebhook、3つの送信先

転機となったのは大きなトラブルではなく、CTOがwebhookベースのメッセージルーティングについて調べていた際にSlackに投稿した一言だった。アイデアはシンプルだ。各自がバラバラにLINEを確認するのではなく、すべてのLINEアカウントを単一のインターフェースで接続し、受信メッセージをチームがすでに使っているツールに転送する。リアルタイム可視化のためにSlack。ログとフォローアップのためにCRM。そして定型的な質問には、送信前にチームがレビューできるAI下書きを用意する。

チームはUnifyPortにLINEアカウントをQRコード認証で接続した――これはLINEがプラットフォーム上でサポートする唯一の認証モードだ。各アカウントのスキャンに要したのは約30秒。1時間以内に6アカウントすべてが稼働し、単一のwebhookエンドポイントで、受信するすべてのLINEメッセージをmessage.receivedイベントとして受信できるようになった。

webhookのペイロードは以下の通りだ。

{
  "id": "evt_9a3f7c2e01",
  "type": "message.received",
  "provider": "line",
  "account_id": "acc_4b82d1",
  "occurred_at": "2026-05-12T01:47:23Z",
  "data": {
    "conversation": {
      "id": "U4af2c891...",
      "type": "user",
      "title": "Tanaka-san"
    },
    "sender": {
      "id": "U4af2c891...",
      "type": "user",
      "name": "Tanaka-san"
    },
    "message": {
      "id": "msg_18420...",
      "type": "text",
      "text": "The CSV export keeps failing — is this a known issue?",
      "direction": "inbound",
      "sent_at": "2026-05-12T01:47:22Z"
    },
    "event": {
      "kind": "message_received"
    }
  }
}

すべての配信にはX-Device-Signatureヘッダーが付与される。これはタイムスタンプとリクエストボディを連結した文字列を、webhookのsigning_secretで署名したHMAC-SHA256のhexダイジェストだ。チームのExpressハンドラーは、処理に入る前に必ず署名を検証する。

import crypto from 'crypto';
import express from 'express';

const app = express();
const SECRET = process.env.WEBHOOK_SIGNING_SECRET;

app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const timestamp = req.get('X-Device-Timestamp') ?? '';
  const signature = req.get('X-Device-Signature') ?? '';
  const hmac = crypto.createHmac('sha256', SECRET);
  hmac.update(timestamp + '.');
  hmac.update(req.body);
  const expected = hmac.digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).end();
  }

  const event = JSON.parse(req.body.toString('utf8'));
  if (event.type === 'message.received') {
    await routeMessage(event);
  }
  res.status(200).end();
});

ここで本質的なのはwebhookハンドラーそのものではない――どのインテグレーションにもあるものだ。重要なのは検証の後に実行されるrouteMessage関数だった。すべてのメッセージを1つのキューに放り込むのではなく、メッセージ内容と会話コンテキストを判定して、どこに送るべきかを決定するルーティングロジックが組み込まれていた。

ルーティングロジック:Slack、CRM、AI――あるいは全部

チームのルーティングルールは最初の2週間で何度も試行錯誤されたが、最終的には3つのティアに落ち着いた。

ティア1 ―― Slack通知(全メッセージ)。 受信したすべてのLINEメッセージが、送信者名・メッセージプレビュー・account_idとともにSlackの#support-lineに投稿される。チームメンバーはどの担当者のアカウントに届いたメッセージかがひと目でわかる。これだけで「メッセージが誰かのスマホに放置される」問題は解決した。アカウント所有者が会議中でも、チームの他のメンバーがメッセージを確認し、UnifyPortのPOST /v1/messagesエンドポイントを使って、元メッセージのreply_to.reply_tokenで代わりに返信できる。

ティア2 ―― CRMログ(全メッセージ)。 並行して実行されるAPIコールが、メッセージ全文・タイムスタンプ・会話IDをCRMの連絡先レコードに作成または更新する。これ以前は、LINE上での顧客とのやり取りはCRMからは完全に不可視だった――各人のスマホのチャット履歴にしか存在しなかったのだ。いまでは、すべてのLINE会話がメールやウェブチケットの履歴と並んで、完全な監査証跡として記録されている。

ティア3 ―― AI下書き(定型質問のみ)。 シンプルなキーワード分類器が、受信メッセージをCSVエクスポート・請求・ログイン障害・APIドキュメントリクエストなどの共通トピックリストと照合する。一致したものについては、関連するドキュメントを含むシステムプロンプト付きで社内のLLMエンドポイントに転送される。AIが生成した下書きは#support-draftsチャンネルに「レビューして送信」ボタン付きで投稿される。人間の承認なしに送信されることはない――しかし下書きは数秒で用意されており、担当者がワンクリックで送信できる。

async function routeMessage(event) {
  const { conversation, sender, message } = event.data;
  const accountId = event.account_id;

  // Tier 1: Always notify Slack
  await postToSlack('#support-line', {
    account: accountId,
    sender: sender.name,
    preview: message.text?.slice(0, 120),
    reply_token: message.reply_token,
  });

  // Tier 2: Always log to CRM
  await crm.upsertContact({
    external_id: sender.id,
    platform: 'line',
    name: sender.name,
    last_message: message.text,
    last_message_at: message.sent_at,
  });

  // Tier 3: AI draft for routine questions
  const topic = classifyTopic(message.text);
  if (topic) {
    const draft = await generateDraft(topic, message.text, sender.name);
    await postToSlack('#support-drafts', {
      sender: sender.name,
      topic,
      draft,
      account_id: accountId,
      reply_token: message.reply_token,
    });
  }
}

classifyTopic関数は意図的にシンプルに設計されている――ニューラル分類器ではなく、キーワードマップだ。「CSV」「export」「download」はエクスポートのドキュメントトピックにマッピングされる。「請求」「料金」「支払い」は請求トピックにマッピングされる。ルールベースにしておくことで、ルーティングの不具合があっても数時間ではなく数分でデバッグできる。

チーム運用のための会話ラベル

2週目にチームが導入したのが、UnifyPortの会話ラベル機能だ。POST /v1/conversations/labelsエンドポイントを使って、チームメンバーごとにラベルを作成した:kenjiyukihirosales。メッセージが届くと、ルーティング関数がそのアカウント所有者のラベルを会話に付与する。アカウント所有者が不在の場合(シンプルなカレンダー連携でチェック)、会話はオンコールの担当者にリラベルされる。

これにより、どのチームメンバーでも、いつでもGET /v1/conversations/labelsを呼び出すだけで、6つのLINEアカウントを横断して自分に割り当てられている会話を正確に把握できるようになった。共有受信箱の問題は、新しい受信箱を作ることではなく、既存の受信箱をまたぐ会話にラベルを付けることで解決したのだ。

このラベルは週次レポートにも活用されている。cronジョブが各担当者のラベルが付いた会話をすべて取得し、レスポンスタイム(message.receivedoccurred_atから最初のPOST /v1/messages呼び出しまでの時間)を集計して、毎週月曜の朝に#support-metricsにサマリーを投稿する。

導入前と4週目

数字がすべてを物語っている。webhook稼働前の週、チームのLINEにおけるレスポンスタイムの中央値は3時間40分で、4時間以上未対応だったメッセージが11件あった。4週目には中央値は22分。4時間以上未対応のメッセージはゼロ――最長でも全社オフサイト中の1時間15分だった。

AI下書きティアは受信メッセージの約35%を処理した。チームはそれらの下書きのうち約80%を修正なしで承認・送信した。残りの20%は軽微な修正が必要だった――たいていは顧客の質問にキーワード分類器が捉えきれないニュアンスがあった場合だ。それでも、下書きがあることで担当者はゼロから書き始める必要がなくなり、文章作成の時間は数分から数秒に短縮された。

CRM連携には予想外の副次効果もあった。営業チームが更新商談の準備にLINEの会話履歴を使い始めたのだ。webhook導入前、更新ミーティングに臨む営業担当には、顧客の過去のLINE上でのサポート対応の記録がなかった。いまでは、すべてのメッセージ、レスポンスタイム、解決内容が自動的にログされており、一目で確認できる。

チームが変えなかったこと

顧客は何の変化も感じなかった。これまでと同じLINEアカウントにメッセージを送り、同じ担当者から返信を受け取っている。変わったのは返信が速くなったことと、いつもの担当者が不在でも他の誰かが対応してくれること――メッセージが1人のスマホに埋もれるのではなく、Slack上でチーム全員に見えるようになっているからだ。

チームはLINEアカウントを個人アカウントのまま使い続けている。OAアカウントには移行していない。UnifyPortのwebhookはアカウントの種類にかかわらず受信メッセージを配信する――OAの認証待ちやインタラクションウィンドウはプッシュ配信に関する制約であり、このチームのワークフローは完全にインバウンドだ。将来的にプロアクティブなキャンペーン(季節プロモーションや新製品案内)を行うことになれば、OAの選択肢も意味を持つだろう。しかしそれまでは、不要な事務手続きを増やす必要はない。

この事例が示すパターン

これはLINE固有の話ではない。どのプラットフォーム――WhatsApp、Telegram、X、Zalo――であっても、複数の個人アカウントで顧客メッセージを受けるチームは、同じルーティングの問題に直面する。webhookはメッセージがどのプラットフォームから来たかを問わない。providerフィールドが変わるだけで、ペイロードの構造、HMAC-SHA256の検証、ルーティングロジックはすべて同じだ。この福岡のチームはすでに、東南アジアの倉庫パートナーとのやり取りに使っているWhatsAppアカウントも同じルーティングシステムに追加することを計画している。1つのハンドラー、1つのラベル体系、1つのメトリクスパイプライン。

もしあなたのチームのサポート戦略が「各自が自分のメッセージを確認する」であるなら、問うべきはより良いプラットフォームが必要かどうかではなく、メッセージにより良いルーティング層が必要かどうかだ。単一のwebhookエンドポイント、20行のルーティングロジック、そしてすでに使っているツールだけで、十分解決できるかもしれない。