跳轉到主要內容
每個 payload 開頭都有 event 欄位指出觸發的事件種類。把 body 當 discriminated union、用這個欄位分流。
type LifecycleWebhookPayload =
  | SessionEndedPayload
  | CallEndedPayload
  | SessionFinalizedPayload
  | RecordingReadyPayload;
每個事件都有的共通欄位:
event
string
Discriminator。session.ended / call.ended / session.finalized / recording.ready 之一。
sessionId
string
Pathors session ID。
timestamp
string
事件 emit 時的 ISO 8601 timestamp。

session.ended

對話結束時觸發。對 call session,通話的最終狀態跟時長此時可能還沒就緒。
currentNodeId
string
對話結束時 agent 停在的 pathway node。
messagesCount
number
這個 session 的訊息總數。
extractedVariables
object
這個 session 提取出來的變數,以名稱為 key。
reason
string
已棄用
event 在多事件支援之前的 legacy 別名。未來版本會移除 —— 新 receiver 請改用 event
{
  "event": "session.ended",
  "sessionId": "2c4f9a13-7e6b-4d8a-9f25-c81e3a7b6d04",
  "timestamp": "2026-05-03T08:42:11.512Z",
  "currentNodeId": "ask_intention",
  "messagesCount": 8,
  "extractedVariables": {
    "name": "Nancy",
    "intention": "book a demo"
  },
  "reason": "session_ended"
}

call.ended

通話結束時觸發。Call session 限定 —— text session 不會收到這個事件。
projectId
string
擁有此 agent 的 project ID。
callStatus
string
最終 domain status。可能值:userHangupagentHanguptransferredvoicemailerrorTransferredbusyuserNoAnsweruserRejectedinvalidNumbersipTrunkFailureagentNoAnswererrorunknownEnded
callDuration
number
通話秒數。對於不計費的結束狀態(busy、no-answer、無效號碼)為 0
{
  "event": "call.ended",
  "sessionId": "2c4f9a13-7e6b-4d8a-9f25-c81e3a7b6d04",
  "projectId": "8f3e2c91-4a7b-4d6e-a23c-9b1f5e8d4c20",
  "timestamp": "2026-05-03T08:42:14.022Z",
  "callStatus": "userHangup",
  "callDuration": 78
}

session.finalized

這個 session 的所有事情都做完時觸發。Payload 已經把原本要從 session.endedcall.ended 各自抓出來的資料合併到同一個 body,receiver 只需處理這一個事件。 對 text session,call 相關欄位(callStatuscallDuration整個被省略(不是設成 null)。用欄位有沒有來區分 call vs text:
if ("callStatus" in payload) {
  // call session — payload.callStatus、.callDuration 都有
} else {
  // text session — call 相關欄位不存在
}
projectId
string
擁有此 agent 的 project ID。
currentNodeId
string
對話結束時 agent 停在的 pathway node。
messagesCount
number
這個 session 的訊息總數。
extractedVariables
object
這個 session 提取出來的變數。
callStatus
string
Call session 限定。call.endedcallStatus。Text session 被省略。
callDuration
number
Call session 限定。call.endedcallDuration。Text session 被省略。
Call session 範例:
{
  "event": "session.finalized",
  "sessionId": "2c4f9a13-7e6b-4d8a-9f25-c81e3a7b6d04",
  "projectId": "8f3e2c91-4a7b-4d6e-a23c-9b1f5e8d4c20",
  "timestamp": "2026-05-03T08:42:14.300Z",
  "currentNodeId": "ask_intention",
  "messagesCount": 8,
  "extractedVariables": {
    "name": "Nancy",
    "intention": "book a demo"
  },
  "callStatus": "userHangup",
  "callDuration": 78
}
Text session 範例:
{
  "event": "session.finalized",
  "sessionId": "5d8e1f2a-9c4b-4e7d-b3a8-6f9c2d5e8a01",
  "projectId": "8f3e2c91-4a7b-4d6e-a23c-9b1f5e8d4c20",
  "timestamp": "2026-05-03T08:48:15.288Z",
  "currentNodeId": "start",
  "messagesCount": 10,
  "extractedVariables": { "name": "Nancy" }
}

recording.ready

通話錄音處理完成後觸發 —— 成功與失敗都會觸發Call session 限定,text session 不會收到。 跟 lifecycle 事件不同,這個事件session.finalized 獨立 —— 它不會 gate finalization,依錄音處理耗時長短,可能在 session.finalized 之前或之後到。
projectId
string
擁有此 agent 的 project ID。
success
boolean
錄音是否成功處理並存檔。為 false 時不帶 recordingUrl
recordingUrl
string
合併後音檔的臨時下載連結。只有 successtrue 時才有。連結為短期有效(約 15 分鐘)—— 請儘快下載。過期後目前沒有 API 可重新取得,請改從 Pathors 後台的通話記錄下載錄音。
成功範例:
{
  "event": "recording.ready",
  "sessionId": "2c4f9a13-7e6b-4d8a-9f25-c81e3a7b6d04",
  "projectId": "8f3e2c91-4a7b-4d6e-a23c-9b1f5e8d4c20",
  "timestamp": "2026-05-03T08:42:30.114Z",
  "success": true,
  "recordingUrl": "https://recordings.pathors.com/recordings/projects/8f3e2c91.../sessions/2c4f9a13.../audio.ogg?..."
}
失敗範例:
{
  "event": "recording.ready",
  "sessionId": "2c4f9a13-7e6b-4d8a-9f25-c81e3a7b6d04",
  "projectId": "8f3e2c91-4a7b-4d6e-a23c-9b1f5e8d4c20",
  "timestamp": "2026-05-03T08:42:30.114Z",
  "success": false
}

實作範例

event 欄位 dispatch 各事件的 receiver:
import express from "express";

const app = express();
app.use(express.json());

app.post("/pathors-webhook", async (req, res) => {
  const payload = req.body;

  switch (payload.event) {
    case "session.ended":
      await onSessionEnded(payload);
      break;

    case "call.ended":
      await onCallEnded(payload);
      break;

    case "session.finalized":
      if ("callStatus" in payload) {
        await onCallCompleted(payload);
      } else {
        await onTextCompleted(payload);
      }
      break;

    case "recording.ready":
      if (payload.success && payload.recordingUrl) {
        await onRecordingReady(payload); // 趁連結過期前抓檔
      }
      break;

    default:
      // 未知事件 — 接收後忽略,這樣未來 Pathors 加新事件時你的 receiver
      // 不會壞掉。
      break;
  }

  res.status(200).json({ status: "ok" });
});

順序

在 lifecycle 事件(session.endedcall.endedsession.finalized)之中,session.finalized 對某個 session 一定最後到。 Call session 的典型順序:
  1. session.ended
  2. call.ended
  3. session.finalized
Text session 則是 session.endedsession.finalized 緊接著到。 session.endedcall.ended 在 edge case(例如使用者直接掛斷在 agent 還沒走到結尾節點時)沒有嚴格順序 recording.ready 不在這個順序內。它不會 gate session.finalized,可能在它之前或之後到 —— 不要因為收到 session.finalized 就假設錄音已經就緒。