サイトウィジェット

ウィジェットは、ボットのチャットをそのままサイトに置いたものです。ページの隅にある丸いボタンから会話が開きます。訪問者にとっては連絡手段がひとつ増えるだけ、ボットにとってはメッセンジャーと同じリアクションと同じオペレーター対話を使う普通のチャネルです。マルチチャネルの概要は チャネル を参照してください。

設置作業は、サイトのテンプレートにコードを 1 か所貼り付けるだけです。それ以外はすべて管理画面で設定し、サイトに触れずに反映されます。

設置コードの入手先

管理画面で ウィジェットサイトへの設置 ブロック → 貼り付け用コード 欄を開きます。コピー ボタンで断片全体がクリップボードに入ります。

コードは次のような形です。省略記号の部分にはご自身のキーが入ります。

<script>
  window.mybot = { key: "eu-1a2b3c4d-..." };
</script>
<script async src="https://getmybot.dev/loader.js"></script>

コードの代わりに「このボットにはインストールキーがまだ発行されていません」と表示される場合、サイトウィジェットのチャネルが未接続です。サポートにご連絡ください。キーはチャネル接続時に発行されます。

コード欄の下には、ウィジェットが接続するホストのヒントが表示されます。サイトに CSP(Content Security Policy)を設定している場合は、そのホストからのスクリプト読み込みと通信を許可してください。許可しないとブラウザーが黙ってウィジェットを遮断します。

コードを置く場所

この断片は、ウィジェットを表示したい すべてのページ の HTML に入れます。実際には共通テンプレートに一度だけ入れる形になります。フッター、CMS の「</body> の直前のコード」欄、タグマネージャーのコンテナなどです。

ルールはわずかです。

  • 最適な位置は 閉じタグ </body> の直前 です。<head> でも動きますが、その場合ブラウザーは本文より先にウィジェットに時間を使います。
  • 2 つのタグの順序は重要です。最初がキーを設定し、次がウィジェットを読み込みます。入れ替えたり、離れた場所に分けたりしないでください。
  • 2 つ目のタグには async が付いており、描画を妨げません。この属性は外さないでください。
  • 1 ページにつき 1 断片です。2 つ貼ると起動を 2 回試みます。

あとはローダーが、そのボットがどのデータセンターで運用されているかを判断し、そこからウィジェット本体を読み込みます。そのために必要な操作はありません。

インストールキーは公開情報です

最初のタグにあるキーは ボットの公開識別子 であり、パスワードではありません。ページのソースに含まれるため、訪問者は誰でも「ページのソースを表示」で読めます。どのチャットウィジェットも同じ仕組みで、これは正常な状態です。

押さえておくべき点:

  • このキーで管理画面にログインしたり、他人の対話を読んだり、購読者リストを書き出したり、ボットの設定を変えたりすることは できません。開けるのはただ 1 つ、このボットとの新しい会話を始めることだけです。
  • キーを秘密として扱わないでください。隠したり難読化したりしても意味はありません。
  • キーを差し替える必要がある場合(取引先と関係を解消したときなど)はサポートが対応します。差し替え後は古いコードが動かなくなるので、貼り付けを更新してください。

許可ドメイン一覧

ウィジェットには 許可ドメイン一覧(origin allowlist)があります。ウィジェットの動作を許可するサイトのアドレス一覧です。チャネル接続時に設定し、その後はキーを再発行せずに変更できます。サイトに貼ったコードはそのままで構いません。

各項目は オリジン です。スキーム、ホスト、そして標準以外ならポートを含みます。

https://example.com
https://www.example.com
https://shop.example.com
http://localhost:3000

照合は 完全一致、1 文字単位です。

  • ワイルドカードはありません。https://*.example.com は機能しません。サブドメインは個別に列挙してください。
  • example.comwww.example.com別項目 です。両方で開くサイトなら両方登録します。
  • http://https:// も別項目です。通常は https:// だけで足りますが、http:// のテスト環境は明示的に追加が必要です。
  • パスは書けません。https://example.com/shop は拒否されます。オリジンはサイトのアドレスだけです。

実際のドメインが一覧にないと、ウィジェットは起動しません。 訪問者のブラウザーは「origin not allowed」という拒否を受け取り、チャットはそもそも開きません。新しいドメインへの移転、サブドメインの追加、別アドレスでの第 2 言語版公開のあとに起きがちです。いずれも項目を個別に追加します。

一覧が の場合は制限がなく、どのサイトからでも動作します。これは「意図的に開放している」ではなく「まだ設定していない」状態と考えてください。自分のドメインが分かった時点で登録しましょう。

この制限で守れること・守れないこと

守れること: 他人のサイトがあなたのウィジェットを埋め込めなくなります。制限がなければ、誰かがコードを自分のページにコピーし、実在の訪問者のブラウザーがボットとの会話内容をそのサイトへ渡してしまいます。一覧はまさにこれを塞ぎます。

守れないこと: キーをコピーしてブラウザー以外から、たとえばスクリプトで直接プラットフォームを呼ぶ相手には効きません。サイトアドレスを示すヘッダーを付けるのはブラウザーであり、ブラウザー外のプログラムは送らないことも、任意の値を送ることもできます。したがって、この一覧は埋め込みの制限であって、セキュリティ境界ではありません。悪用を防ぐのはプラットフォーム側のレート制限と対話の監視であり、この一覧ではありません。

外観

ウィジェット外観 で設定できるのは次の 3 つです。

  • 位置 — 画面左下または右下のボタン。
  • アクセントカラー — ボタンとチャット要素の色。サイトのブランドカラーにすると浮きません。
  • チャット名 — 起動ボタンのラベルとチャットパネルの見出し。通常は会社名か、ボットが名乗る名前です。

隣には プレビュー があり、保存前に結果を実物のウィジェットで確認できます。

ウィジェットにあいさつメッセージはまだありません。設定できるのはこの 3 つだけです。最初のメッセージは、訪問者の発言に対する応答として、通常のリアクションのルールどおりに送られます。

変更はサイト側で自動的に反映されます。ウィジェットはページ読み込み時に設定を読むため、貼り付けたコードを編集する必要はありません。

訪問者は匿名です

訪問者は何も入力せずにチャットを始められます。初回起動時にウィジェットはプラットフォームから 匿名の訪問者 ID を受け取り、ブラウザーに保存します。再訪時に空のチャットではなく、前回の会話が表示されるようにするためです。

ここから次のことが言えます。

  • 対話画面ではこの訪問者は匿名として表示されます。本人が書かない限り、名前も電話番号もメールもありません。
  • ID は特定のブラウザーの中にだけ存在します。別のブラウザー、別の端末、サイトデータの消去はいずれも「履歴のない新しい訪問者」を意味します。
  • 匿名の訪問者を、すでに把握している顧客(会員エリアにログイン済みのユーザーなど)と結び付けることができます。方法は次の節で説明します。

訪問者を自社のユーザーと結び付ける

訪問者がすでにサイトにログインしている場合、その人が誰かをプラットフォームに伝えられます。結び付けたあと、会話は匿名ではなくなります。その人のプロフィールに統合され、それ以前の匿名のメッセージも失われません。

呼び出しは 1 回だけです。証明を作る関数を渡します。

mybot.identify(async (visitorId) => {
  const res = await fetch("/mybot-sign", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ userId: currentUser.id, visitorId }),
  });
  return res.json(); // { userId, signature, expiresAt }
});

ウィジェットは、この関数を visitorId(このブラウザーの現在の匿名訪問者 ID で、ウィジェットだけが知っている唯一の値です)を渡して呼び出し、{ userId, signature, expiresAt } を返す、あるいはそれに解決されるのを待ちます。この関数の唯一の仕事は、visitorId自社サーバー に渡し、ログイン中のユーザー ID(上の currentUser.id は、あなたのサイトでの実際の値に置き換えてください)と一緒に送り、サーバーが返した値をそのまま返すことです。実際の署名処理はこの関数の中ではなく、自社サーバー側で行われます — 詳しくは後述します。

コールバックである理由は、あらかじめ計算済みの値でも、visitorId を直接読み取る手段でもない、次の 2 点です。

  • タイミング。 ウィジェットは起動中に非同期で visitorId を生成します。ローダースクリプトが実行された瞬間にはまだ存在せず、mybot.identify 自体もそれが完了して初めてインストールされます(詳細は後述)。そのため、ページが mybot.identify を呼び出せる時点では、この関数が受け取る ID は確実に本物です。単純な getter ではこの保証は得られません。ページが 1 行早くそれを読んで何も得られず、なぜ検証に通らないのか手がかりのないまま、署名を静かに生成してしまうことを妨げるものが何もないからです。
  • スコープ。 自社ページのコード自体は、visitorId を保持したり保存したり手動で受け渡したりする必要が一切ありません。必要な 1 回の呼び出しのためだけに、この関数の中だけに存在します。

ウィジェットを離れた瞬間から、visitorId はベアラー資格情報(bearer capability)になります。それに対する署名を取得できた者は誰でも、その訪問者のセッションに統合され得ます。自社サーバー への、自社の認証済みリクエストの中でのみ送信してください。それ以外の場所には送らず、ログに残さず、第三者に転送せず、クライアント側のアナリティクス呼び出しにも含めないでください。

署名は自社サーバーでのみ計算します

上の関数の中で、自社サーバー — ブラウザーでは決してありません — が署名を計算します。ウィジェットのシークレットを鍵として HMAC-SHA256 したもので、16 進表記の 先頭 32 文字、3 つの値を 1 つのメッセージにまとめたものが対象です。同じ式は管理画面のシークレットの隣、「Signing algorithm」の下にも表示されています。

signature = hex(HMAC-SHA256(key: secret, message: userId + "\n" + visitorId + "\n" + expiresAt)).slice(0, 32)

上の各 + "\n" + は、各パートの間に入る 実際の改行文字 を表しており、バックスラッシュと n という 2 文字のことではありません。HMAC 関数がメッセージを 1 本の文字列として受け取る実装なら、3 つのパートを本物の改行でつないでください。間に改行を挟まずに .update() を 3 回別々に呼ぶと、まったく違う(誤った)メッセージのハッシュになってしまいます。

3 つのパート:

  • userId — ログイン中のユーザー ID で、自社サーバーが上の関数から受け取り、レスポンスの中で返す値と同じです。
  • visitorId — 関数が引数として受け取り、変更せずに自社サーバーへ転送した匿名訪問者 ID です。
  • expiresAt — この署名が有効であり続ける期限を示す、秒単位の unix タイムスタンプ(ミリ秒ではありません)で、署名時に自社サーバーが発行します。プラットフォームは、expiresAt がすでに過去のもの、または 24 時間を超えて 未来のものである呼び出しを拒否します。一度計算してキャッシュし後続のリクエストで使い回すのではなく、関数から返す直前に署名してください。

この有効期限こそが、今回の変更の本質であり、単なる細部ではありません。以前の式はユーザー ID しかカバーしていなかったため、一度捕まえた署名——どこかにログとして残っていた、通信を盗聴して得た、経緯は何であれ——は永久に有効なままで、発行対象だった訪問者に限らず どの 訪問者に対しても通用してしまいました。それを手に入れた者は、まったく別のブラウザーで再生でき、プラットフォームは見知らぬ人物の匿名の閲覧履歴を実在の顧客のプロフィールに統合してしまいます。署名を特定の visitorId に紐付け、短い寿命を与えることで、この穴の両端がふさがれます。署名は発行されたセッションに対してのみ検証を通り、それ以外では通らず、expiresAt が過ぎればまったく検証を通らなくなります。つまり万一署名が漏れても、それは恒久的な脅威ではなく、1 つのセッションに限られた短命なリスクにとどまります。

3 つの値すべてを自社サーバーで計算し、関数から計算済みの状態で返してください。 これは形式的な注意ではありません。ブラウザーで署名を計算するにはシークレット自体を送り込む必要があり、それはページの全訪問者に手渡すのと同じです。そうなれば誰でも任意の顧客になりすまし、その会話を読めてしまいます。署名(それが計算された visitorIdexpiresAt を含む)はブラウザーに返して安全ですが、シークレットは違います。

呼び出し自体は画面に何も表示しません。結び付けはサーバー側で静かに行われます。関数がエラーを投げるか Promise が reject された場合、または結果の署名をプラットフォームが拒否した場合(一致しない、expiresAt が欠けている、すでに過去である、24 時間を超えて未来である、のいずれか)は、それ以上何も起こりません。訪問者は何も気付かず匿名のまま会話を続けます。

mybot.identify はすぐには存在せず、ウィジェット本体のバンドルが読み込まれ、本物の visitorId がそろった時点で現れます(上の「タイミング」を参照)。<head> の先頭行ではなく、ページの読み込み完了ハンドラーから呼び出してください。

シークレットの場所と交換の手順

ウィジェットのシークレットは、署名に使う鍵です。ウィジェットの設定の 「identify() secret」 セクションにあります。そのセクションにウィジェットが未接続と表示される場合は、先に Web チャネルを接続してください。シークレットはそれと同時に現れます。

表示。 「Reveal secret」 ボタンで値を取得します。値は平文では出ません。まず点で表示され、文字を出すのは別の切り替え 「Show」 です(戻すのは 「Mask」)。隣には値を画面から消す 「Clear from screen」 があります。

表示は何度でも可能で、表示しても何も無効になりません。画面を閉じたあともシークレットは有効なままです。これはハッシュではなく暗号化して保存しているためで、署名を毎回検証するには実際の値が必要だからです。半年後に新しいバックエンドを配置するときも、また見に来れば済みます。

コピーしたあとの後始末はご自身で。クリップボードを当社側から消すことはできません。 ブラウザーから確実に行う方法がないため、できるとは言いません。作業が終わったら値を画面から消し、クリップボードは手動で空にしてください。共用のコンピューターでは特にそうしてください。

交換。 確認ステップ付きの 「Rotate secret」 ボタンです。これは 即座に壊れる 操作です。新しいシークレットができた瞬間に、古いほうは検証に通らなくなります。重複期間はなく、両方の鍵が有効な瞬間は存在しません。

壊れるのは訪問者を自社ユーザーに結び付ける処理だけです。会話は中断されません。訪問者は匿名のまま書き込み、返信も受け取れます。バックエンドが新しいシークレットで署名を始めれば元通りです。設置コードと許可ドメイン一覧は変わらないため、サイト側で変更するものはありません。

したがって交換は意図をもって行ってください。まず新しい値を使うバックエンドの配置を準備し、そのうえで「Rotate secret」を押します。どうなるか試すために押すのは避けてください。交換直後に新しい値が画面に表示されるので、その場でコピーできます。

ページ上での挙動

ウィジェットはサイトと干渉しないよう意図的に作られています。

  • 隔離されたコンテナ(クローズドな Shadow DOM)の中で動きます。あなたの CSS はウィジェット内部に届かず、ウィジェットのスタイルもページに漏れません。副作用として、独自 CSS でウィジェットの見た目を変えることはできません。外観設定をお使いください。
  • コンテナは画面全体に広がりますが、クリックを奪いません。クリックはページへ素通りし、反応するのはボタンとチャットパネルだけです。
  • ウィジェットは常にページ内容の上に表示され、独自のレイヤーで覆い隠すことはできません。
  • ウィジェット内部のエラーは内部で留まります。最悪でもチャットボタンが動かないだけで、サイトは動き続けます。表示されない場合はブラウザーのコンソールを確認してください。ほとんどは CSP か、一覧に未登録のドメインです。

ウィジェットが表示されない場合

順に確認します。

  1. ページのソースを開き、2 つのタグがあること、キーが空でないことを確認します。
  2. サイトのアドレスが https://www も含めて一覧の記載と完全に一致するか確認します。
  3. ブラウザーのコンソールを見ます。Content Security Policy に関するメッセージが出ていれば、サイトの CSP でプラットフォームのホストを許可する必要があります。
  4. 広告ブロッカーがウィジェットを削除していないか、拡張機能なしのプライベートウィンドウで確認します。

次に読む