網站小工具

小工具就是把機器人的聊天直接放到你的網站上:頁面角落一顆圓形按鈕,點開即是對話。對訪客而言只是多了一個聯絡你的方式;對機器人而言則是一個普通頻道——與通訊軟體相同的反應、相同的客服對話。多頻道概覽請見頻道

安裝就是把一段程式碼貼進網站範本。其餘全部在後台設定,不必再改動網站。

在哪裡取得安裝程式碼

在後台開啟 小工具安裝到網站 區塊 → 嵌入程式碼 欄位。複製 按鈕會把整段程式碼放進剪貼簿。

程式碼長成這樣,省略號處是你自己的金鑰:

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

如果看到的不是程式碼,而是「尚未為此機器人核發安裝金鑰」,代表網站小工具頻道尚未接上。請聯絡客服:金鑰在接上頻道時核發。

程式碼下方有一行提示,說明小工具會連到哪個主機。若你的網站啟用了 CSP(內容安全政策),請允許該主機載入指令碼並發出網路請求,否則瀏覽器會無聲地擋掉小工具。

程式碼放在哪裡

這段程式碼要放進 每一個 需要顯示小工具的頁面的 HTML。實務上就是在網站的共用範本裡放一次——頁尾、CMS 的「</body> 之前的程式碼」欄位,或標籤管理工具的容器。

規則很短:

  • 最佳位置是 結尾的 </body> 之前。放在 <head> 也能運作,但瀏覽器會在你的內容之前先花時間處理小工具。
  • 兩個標籤的順序很重要:第一個設定金鑰,第二個載入小工具。不要對調,也不要拆散放在頁面不同位置。
  • 第二個標籤帶有 async,不會阻擋渲染。請保留這個屬性。
  • 每頁一份。放兩份等於嘗試啟動兩次。

接下來由載入器自行判斷你的機器人由哪個資料中心提供服務,並從那裡取回小工具本體。這一步不需要你做任何事。

安裝金鑰是公開的

第一個標籤裡的金鑰是 機器人的公開識別碼,不是密碼。它就在頁面原始碼裡:任何訪客開啟「檢視網頁原始碼」都看得到。所有聊天小工具都是如此,屬於正常現象。

需要理解的是:

  • 用這把金鑰 無法 登入後台、閱讀他人的對話、匯出訂閱者名單,也無法更動機器人的任何設定。它只開啟一件事:與這個機器人展開新的對話。
  • 不要把金鑰當成機密:藏起來或混淆它毫無意義。
  • 若需要更換金鑰(例如與外包廠商結束合作),由客服處理。更換後網站上的舊程式碼會失效,必須更新。

允許網域清單

小工具有一份 允許網域清單(origin allowlist)——允許小工具運作的網站位址。清單在接上頻道時設定,之後可單獨修改而不必重新核發金鑰:你網站上的程式碼維持不變。

清單中的每一筆都是一個 來源(origin):協定加主機,若連接埠非預設再加上連接埠。

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

比對是 逐字元精確 的:

  • 沒有萬用字元。https://*.example.com 不會生效——請逐一列出子網域。
  • example.comwww.example.com兩筆不同 的資料。若網站兩個位址都能開,兩筆都要加。
  • http://https:// 同樣是不同的資料。通常只需要 https://,但 http:// 上的測試環境必須明確加入。
  • 不接受路徑:https://example.com/shop 會被拒絕,來源只包含網站位址。

如果你真正的網域不在清單裡,小工具不會啟動。 訪客的瀏覽器會收到「origin not allowed」的拒絕,聊天根本不會開啟。這通常出現在搬遷到新網域、新增子網域,或在獨立位址上線第二個語言版本之後——每一種都需要各自新增一筆。

若清單 是空的,就沒有任何限制,小工具可從任何網站運作。請把它視為「尚未設定」,而不是「刻意開放」:一旦確定了自己的網域,就把它們填上。

這項限制擋得住什麼、擋不住什麼

擋得住: 別人的網站無法把你的小工具嵌到自己頁面上。否則有人可以把你的程式碼複製到自家頁面,真實訪客的瀏覽器就會把與你機器人的對話內容交給那個網站。清單堵住的正是這一點。

擋不住: 它攔不住已取得金鑰、直接向平台發送請求的人——不是透過瀏覽器,而是例如用指令碼。帶有網站位址的標頭是瀏覽器加上的;瀏覽器之外的程式可以不送,也可以任意偽造。所以請把這份清單視為嵌入限制,而不是安全邊界。防止濫用的是平台端的頻率限制與對話審核,而不是這份清單。

外觀

小工具外觀 中可設定:

  • 位置 —— 按鈕在左下角或右下角。
  • 強調色 —— 按鈕與聊天元素的顏色。使用品牌色,小工具才不會顯得突兀。
  • 聊天名稱 —— 啟動按鈕上的文字與聊天面板標題。通常是公司名稱,或機器人自我介紹時使用的名字。

旁邊有 預覽 —— 一個即時小工具,儲存前就能看到結果。

小工具目前還沒有歡迎訊息:可設定的只有上述三項。第一則訊息由機器人在訪客開口後,依你平常的反應規則送出。

變更會自行生效:小工具在頁面載入時讀取設定,貼入的程式碼永遠不必修改。

訪客是匿名的

訪客不必填任何欄位就能發訊息。首次啟動時,小工具會從平台取得一組 匿名訪客識別碼 並存進瀏覽器,讓對方再次到訪時看到自己既有的對話,而不是空白聊天。

由此可知:

  • 在對話清單中,這類訪客顯示為匿名:在他自己寫出來之前,沒有姓名、電話與電子郵件。
  • 識別碼只存在於某一個瀏覽器中。換瀏覽器、換裝置或清除網站資料,都代表一位紀錄全新的訪客。
  • 匿名訪客可以與你已經認識的客戶連結起來,例如已登入會員區的使用者。做法見下一節。

把訪客連結到你自己的使用者

若訪客已在你的網站登入,你可以告訴平台他是誰。連結之後,對話就不再匿名:它會併入這個人的檔案,先前的匿名訊息也不會遺失。

一次呼叫即可,附帶一個負責產生證明的函式:

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——這個瀏覽器目前的匿名訪客識別碼,唯一只有小工具才知道的值——呼叫你的函式,並等待它回傳,或解析為,{ userId, signature, expiresAt }。你的函式唯一要做的事,是把 visitorId 連同已登入使用者的識別碼(上面的 currentUser.id 換成你網站上實際的值)一起交給你自己的伺服器,再原樣把伺服器的回應傳回。真正的簽章發生在你的伺服器上,不是在這個函式裡——見下文。

之所以是一個回呼,而不是預先算好的值,也不是直接讀取 visitorId 的方式,有兩個原因:

  • 存在的時機。 小工具在啟動過程中非同步產生 visitorId——載入指令碼執行的那一刻它還不存在,mybot.identify 本身也要等這個過程結束後才會安裝(詳見下文)。所以到你的頁面能夠呼叫 mybot.identify 的那一刻,你的函式收到的識別碼必然是真實的。普通的 getter 不會有這種保證:沒有什麼能阻止頁面提早一行讀取它而一無所獲,悄無聲息地產生一個永遠對不上、又沒有任何線索能說明原因的簽章。
  • 作用範圍。 你自己頁面的程式碼完全不需要手動持有、儲存或傳遞 visitorId——它只存在於這一個函式內部,只為它需要發起的那一次呼叫而存在。

visitorId 一旦離開小工具,就成了一種持有即生效的憑證(bearer capability):任何能為它取得簽章的人,都可能被併入那位訪客的工作階段。只把它傳給你自己的伺服器,透過你自己經過身分驗證的請求,其他任何地方都不要傳——不要記進日誌,不要轉發給第三方,也不要放進用戶端的分析呼叫裡。

簽章只在你自己的伺服器上計算

在你上面的函式內部,你自己的伺服器——絕不是瀏覽器——負責計算簽章:以小工具密鑰做的 HMAC-SHA256,取十六進位的 前 32 個字元,對象是拼成一則訊息的三個值。同一個公式也顯示在後台密鑰旁邊的「Signing algorithm」下方:

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

上面每一處 + "\n" + 都代表各部分之間一個真正的換行字元,而不是反斜線加 n 這兩個字元。若你的 HMAC 函式接收的是單一字串,就用真正的換行把三部分接起來——分三次呼叫 .update() 而中間不插入換行,會對一則不同、錯誤的訊息求雜湊。

三個部分:

  • userId —— 已登入使用者的識別碼,與你的伺服器從上面那個函式收到、並在回應裡原樣傳回的值相同。
  • visitorId —— 你的函式作為引數收到、並原封不動轉發給你伺服器的匿名訪客識別碼。
  • expiresAt —— 以秒為單位的 unix 時間戳記(不是毫秒),由你的伺服器在簽章時產生,標記這則簽章的有效截止時刻。平台會拒絕 expiresAt 已經過去的呼叫,也會拒絕比目前時間超過 24 小時的呼叫——請在從函式回傳之前才簽章,不要簽一次後續請求反覆重複使用。

這個有效期正是這次改動的核心,而不是次要細節。舊公式只涵蓋使用者識別碼,所以一則簽章一旦被擷取——不論是記在了某處日誌裡,還是從流量中截獲的,方式並不重要——就永遠有效,且對任何訪客都成立,而不只是對當初簽發的那一位。誰拿到了它,都能在完全不同的瀏覽器裡重放,平台就會把陌生人的匿名瀏覽紀錄併入真實客戶的檔案。把簽章綁定到特定的 visitorId、再給它一個短壽命,就把這個漏洞的兩端都堵上了:簽章只對簽發時的那次工作階段有效,別處一律不認;一旦過了 expiresAt,它就徹底失效——所以即便簽章外洩,也只是限於單一工作階段的短暫風險,而非長期隱患。

請在你的伺服器上把三個值都算好,從函式裡以已經算好的狀態回傳。 這不是形式:要在瀏覽器裡算簽章,就得把密鑰本身送到瀏覽器,等於交給頁面的每一位訪客。此後任何人都能冒充你的任一客戶並讀取其對話。把簽章(以及用來計算它的那對 visitorId/expiresAt)交還給瀏覽器是安全的,密鑰不是。

呼叫本身在畫面上沒有任何提示:連結發生在伺服器端,安靜完成。若你的函式擲出錯誤或其 promise 被拒絕,或平台拒絕了得到的簽章——對不上,或 expiresAt 缺失、已經過去,或比目前時間超過 24 小時——那也就到此為止:訪客毫無察覺,繼續以匿名身分聊天。

mybot.identify 不會立刻存在——它要等小工具主程式載入完成、帶著真實的 visitorId 一起才會出現(見上面的「存在的時機」)。請在頁面載入的回呼中呼叫,而不要寫在 <head> 的第一行。

密鑰在哪裡,以及如何更換

小工具密鑰就是你用來簽章的那把鑰匙。它在小工具設定的 「identify() secret」 區塊裡。若該區塊提示小工具尚未接上,請先接上 web 頻道——密鑰會隨之出現。

檢視。「Reveal secret」 按鈕取回值。它不會直接以明文出現:你先看到的是圓點,顯示字元要用另一個開關 「Show」「Mask」 再遮回去)。旁邊還有 「Clear from screen」,把值從畫面上移除。

可以重複檢視,而且檢視不會讓任何東西失效:關掉這個畫面之後密鑰依然有效。之所以如此,是因為它以加密而非雜湊的方式儲存——平台每次驗證簽章都需要它的真實值。半年後你部署新後端時,回來再看一次即可。

複製之後請自己收尾。剪貼簿平台清不掉:在瀏覽器裡沒有可靠的做法,所以我們也不作此承諾。用完後把值從畫面上移除,並手動清空剪貼簿,在共用電腦上尤其如此。

更換。 「Rotate secret」 按鈕,帶確認步驟。這是 立刻生效的破壞性 操作:新密鑰一旦產生,舊的當下就無法通過驗證。沒有重疊視窗,也不存在兩把鑰匙同時有效的瞬間。

被中斷的正是把訪客連結到你的使用者這一環。對話不會中斷——訪客照常發訊息、照常收到回覆,只是以匿名身分——直到你的後端改用新密鑰簽章。安裝程式碼與允許網域清單都不受影響,你的網站上無需更動任何東西。

因此更換要有意為之:先準備好使用新值的後端發佈,然後再按「Rotate secret」。為了看看會發生什麼而按下去,是個壞主意。更換後新值會立即顯示在畫面上,可以當場複製。

小工具在頁面上的行為

小工具刻意設計成不與你的網站互相干擾:

  • 它活在 隔離容器 中(封閉的 Shadow DOM)。你的樣式進不去,小工具的樣式也出不來。副作用是:你無法用自己的 CSS 改它的外觀——請使用外觀設定。
  • 容器鋪滿整個畫面,但 不攔截點擊:點擊會穿透到頁面,只有按鈕與聊天面板本身會回應。
  • 小工具永遠位於頁面內容之上,你的圖層蓋不住它。
  • 小工具內部的任何錯誤都留在內部:最糟也只是聊天按鈕失效,你的網站照常運作。若小工具沒出現,請看瀏覽器主控台——多半是 CSP,或是網域沒加進清單。

若小工具沒有出現

依序檢查:

  1. 開啟網頁原始碼,確認兩個標籤都在,且金鑰不是空的。
  2. 確認網站位址與清單中的寫法完全一致,包含 https://www
  3. 查看瀏覽器主控台:出現內容安全政策相關訊息,代表需要在網站 CSP 中放行平台的主機位址。
  4. 確認沒有廣告攔截器把小工具移除——在不帶擴充功能的無痕視窗中試一次。

接下來