Webhooks 請求 (Webhooks request)
當 webhook 事件觸發時,Logto 會向每個已訂閱該事件的端點發送 POST 請求。完整事件目錄請參閱 Webhooks 事件;本頁說明 Logto 傳送的請求結構。
請求標頭 (Request headers)
| Key | 可自訂 | 說明 |
|---|---|---|
| user-agent | ✅ | 預設為 Logto (https://logto.io/)。 |
| content-type | ✅ | 預設為 application/json。 |
| logto-signature-sha-256 | 請求主體的簽章。詳見 保護你的 webhooks。 |
可自訂標頭可透過 安全 webhook 設定覆寫。
請求主體概覽 (Request body overview)
主體為 JSON 物件。其具體結構取決於事件所屬類別:
| Family | Events | 觸發時機 |
|---|---|---|
| 使用者流程 (User flow) | PostRegister, PostSignIn, PostResetPassword | 使用者完成由 Experience API 處理的註冊、登入或重設密碼流程時。 |
| 資料變更 (Data mutation) | User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.* | 由 Management API 呼叫或 Experience API 上的使用者流程導致底層資料模型變更時。 |
| 例外 (Exception) | Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded | 安全事件,例如連續驗證失敗導致帳號鎖定等。 |
每個類別都包含一組共通欄位。各類別會再加上自身的請求上下文欄位及事件專屬 payload。
共通欄位 (Common fields)
無論類別,所有 webhook 傳送都會包含:
| Field | Type | Optional | 說明 |
|---|---|---|---|
| hookId | string | Logto 中 webhook 設定的識別碼。 | |
| deliveryId | string | 此次傳送的唯一識別碼,對每個 webhook 與事件唯一,且在自動重試期間保持不變。詳見去重複傳送。 | |
| event | string | 觸發此次傳送的事件。 | |
| createdAt | string | Payload 建立時間,ISO 8601 格式。 | |
| userAgent | string | ✅ | 觸發請求的 user-agent。 |
每個類別還會包含觸發請求的 IP 位址:使用者流程事件為 userIp,資料變更與例外事件為 ip。語意相同,名稱差異僅為相容舊版。
去重複傳送 (Deduplicating deliveries)
同一個 delivery 可能多次抵達你的端點:Logto 會重試回應狀態為 500 以上的請求,因此即使你的端點已處理請求但回應 5xx(例如前端閘道因處理緩慢回傳 504),該請求仍會再次送達。每次重試都帶有相同的 deliveryId,而新事件或同一事件發送到不同 webhook 則會有新的 deliveryId。請記錄你已處理過的 deliveryId,並略過已知的 payload。
deliveryId 也是記錄此次傳送的稽核日誌條目的 id,因此你的服務回報的值可在 Console 的 webhook 最近請求中開啟,或用 GET /api/logs/:id 取得。
測試 payload 也帶有 deliveryId,因此同一處理器可處理,但測試請求不會被記錄。
使用者流程事件 payload (User flow event payloads)
事件: PostRegister, PostSignIn, PostResetPassword。
當使用者完成由 Experience API 處理的註冊、登入或重設密碼流程時觸發。除了共通欄位外,主體還包含:
| Field | Type | Optional | 說明 |
|---|---|---|---|
| interactionEvent | 'SignIn' | 'Register' | 'ForgotPassword' | 使用者流程事件型別。分別對應 PostSignIn / PostRegister / PostResetPassword。欄位名稱保留歷史 "interaction" 命名。 | |
| sessionId | string | ✅ | 此事件的 Session ID(非 Interaction ID),如適用。 |
| userIp | string | ✅ | 觸發請求的 IP 位址。 |
| userId | string | ✅ | 此事件關聯的 User ID,如適用。 |
| user | UserEntity | ✅ | 此事件關聯的使用者實體,如適用。 |
| applicationId | string | ✅ | 此事件關聯的 Application ID,如適用。 |
| application | ApplicationEntity | ✅ | 此事件關聯的應用程式實體,如適用。 |
實體結構 (Entity shapes)
type UserEntity = {
id: string;
username?: string;
primaryEmail?: string;
primaryPhone?: string;
name?: string;
avatar?: string;
customData?: object;
identities?: object;
lastSignInAt?: string;
createdAt?: string;
applicationId?: string;
isSuspended?: boolean;
};
enum ApplicationType {
Native = 'Native',
SPA = 'SPA',
Traditional = 'Traditional',
MachineToMachine = 'MachineToMachine',
Protected = 'Protected',
SAML = 'SAML',
}
type ApplicationEntity = {
id: string;
type: ApplicationType;
name: string;
description?: string;
};
資料變更事件 payload (Data mutation event payloads)
事件: 所有 User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.* 事件。完整目錄見 Webhooks 事件 → 資料變更 webhook 事件。
主體固定包含:
- 共通欄位。
- 一個
ip欄位,為觸發請求的 IP 位址(可選,已知時提供)。 - 一個API 上下文,描述變更的觸發方式。根據來源有兩種變體:
- Experience API 上下文:變更來自使用者面向流程。
- Management API 上下文:變更來自直接的 Management API 呼叫。
- 一個事件專屬 payload:受影響的實體於
data欄位,部分事件有額外頂層欄位。詳見事件專屬資料 payload。
Experience API 上下文欄位 (Experience API context fields)
當變更由 Experience API 上的使用者面向流程觸發時(如註冊時的 User.Created 或個資更新時的 User.Data.Updated)會出現。
| Field | Type | Optional | 說明 |
|---|---|---|---|
| interactionEvent | 'SignIn' | 'Register' | 'ForgotPassword' | ✅ | 產生變更的使用者流程事件型別。欄位名稱保留歷史 "interaction" 命名。 |
| sessionId | string | ✅ | 此事件的 Session ID(非 Interaction ID),如適用。 |
| applicationId | string | ✅ | Application ID,如適用。 |
| application | ApplicationEntity | ✅ | 應用程式實體,如適用。 |
Management API 上下文欄位 (Management API context fields)
當變更由 Management API 呼叫觸發時出現。
| Field | Type | Optional | 說明 |
|---|---|---|---|
| path | string | ✅ | 觸發此 webhook 的 API 呼叫路徑。 |
| method | string | ✅ | API 呼叫的 HTTP 方法。 |
| status | number | ✅ | API 呼叫的回應狀態碼。 |
| params | object | ✅ | API 呼叫的 koa 路徑參數。 |
| matchedRoute | string | ✅ | koa 匹配到的路由。Logto 用此欄位比對已啟用的 webhook 事件過濾器。 |
事件專屬資料 payload (Event-specific data payloads)
每個資料變更事件都包含頂層 data 欄位,攜帶受影響的實體;若變更無法以單一實體摘要(如刪除與成員變更事件),則為 null。部分事件還有額外頂層欄位,Organization.Membership.Updated 為一例,詳見下文。
使用者事件 (User events)
| Event | Field | Type | Optional | 說明 |
|---|---|---|---|---|
| User.Created | data | UserEntity | 新建立的使用者實體。 | |
| User.Data.Updated | data | UserEntity | 更新後的使用者實體。 | |
| User.Deleted | data | null | / |
角色事件 (Role events)
type Role = {
id: string;
name: string;
description: string;
type: 'User' | 'MachineToMachine';
isDefault: boolean;
};
type Scope = {
id: string;
name: string;
description: string;
resourceId: string;
createdAt: number;
};
| Event | Field | Type | Optional | 說明 |
|---|---|---|---|---|
| Role.Created | data | Role | 新建立的角色實體。 | |
| Role.Data.Updated | data | Role | 更新後的角色實體。 | |
| Role.Deleted | data | null | / | |
| Role.Scopes.Updated | data | Scope[] | 指派給角色的更新後權限範圍 (scopes)。 | |
| Role.Scopes.Updated | roleId | string | ✅ | 指派權限範圍的角色 ID。(僅於建立角色時預先指派權限範圍時提供) |
權限(Scope)事件 (Permission (Scope) events)
| Event | Field | Type | Optional | 說明 |
|---|---|---|---|---|
| Scope.Created | data | Scope | 新建立的權限範圍實體。 | |
| Scope.Data.Updated | data | Scope | 更新後的權限範圍實體。 | |
| Scope.Deleted | data | null | / |
組織事件 (Organization events)
type Organization = {
id: string;
name: string;
description?: string;
customData: object;
createdAt: number;
};
| Event | Field | Type | Optional | 說明 |
|---|---|---|---|---|
| Organization.Created | data | Organization | 新建立的組織實體。 | |
| Organization.Data.Updated | data | Organization | 更新後的組織實體。 | |
| Organization.Deleted | data | null | / | |
| Organization.Membership.Updated | data | null | / | 變更內容由頂層 delta 陣列描述。詳見Organization.Membership.Updated payload。 |
Organization.Membership.Updated payload
除了共通欄位與適用的 API 上下文欄位(Management API 路由用 Management API 上下文,即時佈建用 Experience API 上下文),Organization.Membership.Updated 事件還會在 payload 頂層(與 event、createdAt 等同層,不在 data 內,該欄位對此事件永遠為 null)帶有 organizationId 及可選的 delta 陣列。
| Field | Type | Optional | 說明 |
|---|---|---|---|
| organizationId | string | 變更成員的組織 ID。 | |
| addedUserIds | string[] | ✅ | 此觸發新增的 User ID。未新增使用者或不影響使用者成員時省略。 |
| removedUserIds | string[] | ✅ | 此觸發移除的 User ID。未移除使用者時省略。 |
| addedApplicationIds | string[] | ✅ | 新增的 Application ID。未新增應用程式或不影響應用程式成員時省略。 |
| removedApplicationIds | string[] | ✅ | 移除的 Application ID。未移除應用程式時省略。 |
四個 delta 陣列皆為可選且附加:對於不預期這些欄位的消費者,既有 payload 結構不變,舊有 data: null 欄位仍會照常輸出。
觸發來源與可能出現的 delta 欄位 (Triggers and which delta fields they may emit)
| 觸發來源 | 可能出現的 delta 欄位 |
|---|---|
POST /organizations/:id/users | addedUserIds |
PUT /organizations/:id/users | addedUserIds, removedUserIds |
DELETE /organizations/:id/users/:userId | removedUserIds |
POST /organizations/:id/applications | addedApplicationIds |
PUT /organizations/:id/applications | addedApplicationIds, removedApplicationIds |
DELETE /organizations/:id/applications/:applicationId | removedApplicationIds |
PUT /organization-invitations/:id/status(Accepted) | addedUserIds |
| 即時佈建新增使用者至新組織時 | addedUserIds |
空 delta 欄位會被省略(缺席 ≠ 空變更) (Empty deltas are omitted (absent ≠ empty change))
空的 delta 陣列會完全省略於 payload。例如,PUT /organizations/:id/users 若將成員集合替換為現有集合,實際上沒有變更,payload 僅剩 { organizationId },四個 delta 欄位皆缺席。重新加入已存在成員、已是成員的邀請再接受亦同。
消費者必須將缺席欄位視為「該側無變更」,而非「空變更」。
單一陣列上限(靜默截斷) (Per-array cap (silent truncation))
每個 delta 陣列上限為5000 筆。若單次 Management API 呼叫新增或移除超過 5000 位使用者(或應用程式),對應陣列會靜默截斷為前 5000 筆。payload 內不會標示已觸發上限。
若你的應用程式會進行大規模批次操作,可能一次影響超過 5000 位成員,請將陣列長度正好為 5000 視為需透過 Management API 重新比對成員:
GET /organizations/:id/users:完整使用者成員清單。GET /organizations/:id/applications:完整應用程式成員清單。
此設計與 GitHub 的 push 事件類似,該事件將 commits 上限設為 20 筆,並引導消費者使用 compare API 取得完整清單。
跳過無實際變更事件 (Skipping no-op events)
要在消費端跳過無實際變更(無 delta 欄位)的傳送,可依 delta 陣列是否存在過濾:
if (
payload.addedUserIds?.length ||
payload.removedUserIds?.length ||
payload.addedApplicationIds?.length ||
payload.removedApplicationIds?.length
) {
// 有實際成員變更,處理之
}
?.length 對 undefined 與 [] 皆為 falsy,因此此判斷式無論欄位缺席或(假設未來)為空陣列都適用。
範例 payload (Example payloads)
新增使用者(POST /organizations/:id/users):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"addedUserIds": ["u_001"]
}
取代使用者成員集合(PUT /organizations/:id/users):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"addedUserIds": ["u_002"],
"removedUserIds": ["u_001"]
}
移除使用者(DELETE /organizations/:id/users/:userId):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"removedUserIds": ["u_001"]
}
新增應用程式(POST /organizations/:id/applications):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"addedApplicationIds": ["app_xyz"]
}
重新加入已存在成員、無實際變更的 PUT 或已是成員的邀請再接受(無實際變更):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc"
}
批次操作觸發 5000 筆上限(靜默截斷):
{
"event": "Organization.Membership.Updated",
"organizationId": "org_abc",
"removedUserIds": ["u_0001", "u_0002", "/* … 共 5000 筆 */"]
}
看到長度正好為 5000 的陣列時,應進行 GET /organizations/:id/users(或 /applications)比對。
組織角色事件 (Organization role events)
type OrganizationRole = {
id: string;
name: string;
description?: string;
};
type OrganizationScope = {
id: string;
name: string;
description?: string;
};
| Event | Field | Type | Optional | 說明 |
|---|---|---|---|---|
| OrganizationRole.Created | data | OrganizationRole | 新建立的組織角色實體。 | |
| OrganizationRole.Data.Updated | data | OrganizationRole | 更新後的組織角色實體。 | |
| OrganizationRole.Deleted | data | null | / | |
| OrganizationRole.Scopes.Updated | data | null | / | |
| OrganizationRole.Scopes.Updated | organizationRoleId | string | ✅ | 指派權限範圍的角色 ID。(僅於建立角色時預先指派權限範圍時提供) |
組織權限(Scope)事件 (Organization permission (scope) events)
| Event | Field | Type | Optional | 說明 |
|---|---|---|---|---|
| OrganizationScope.Created | data | OrganizationScope | 新建立的組織權限範圍實體。 | |
| OrganizationScope.Data.Updated | data | OrganizationScope | 更新後的組織權限範圍實體。 | |
| OrganizationScope.Deleted | data | null | / |
例外事件 payload (Exception event payloads)
事件: Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded。
於安全事件時觸發,例如連續驗證失敗導致帳號鎖定,或因應用程式同時登入裝置數超過上限而撤銷授權。
每個例外事件都帶有共通欄位及 ip 欄位(結構同資料變更事件)。其餘欄位依事件而異。
Identifier.Lockout
來自使用者面向流程,因此主體還包含 Experience API 上下文欄位,並加上:
enum SignInIdentifier {
Email = 'email',
Phone = 'phone',
Username = 'username',
}
| Field | Type | Optional | 說明 |
|---|---|---|---|
| type | SignInIdentifier | 使用者的識別型別,如 email、phone 或 username。 | |
| value | string | 觸發鎖定的使用者識別值。 |
Message.RateLimited
來自使用者面向流程,因此主體還包含 Experience API 上下文欄位,並加上:
| Field | Type | Optional | 說明 |
|---|---|---|---|
| action | string | 被限流的動作,例如 VerificationCodeSend。 | |
| recipient | string | 觸發發送速率限制的 email 或手機號碼。 |
Grant.LimitExceeded
當成功授權導致使用者超過應用程式的最大同時驗證裝置數(maxAllowedGrants)時,Logto 會撤銷該應用程式最舊的授權,並觸發此事件。
此事件由 OIDC 授權端點發出,而非 Experience API,因此不包含 interactionEvent 或 sessionId。除共通欄位與 ip 外,主體還包含:
| Field | Type | Optional | 說明 |
|---|---|---|---|
| userId | string | 被撤銷授權的使用者。 | |
| applicationId | string | 超過 maxAllowedGrants 限制的應用程式。 | |
| application | ApplicationEntity | ✅ | 應用程式實體。若傳送時無法解析應用程式則省略。 |
| maxAllowedGrants | number | 事件觸發時應用程式設定的上限。 | |
| preRevocationActiveGrantCount | number | 撤銷前該使用者於此應用程式持有的有效授權數(含剛發出的那筆)。 | |
| revokedGrantIds | string[] | 實際被撤銷的授權 ID,依舊到新排序。 |
範例 payload:
{
"hookId": "hook_abc",
"deliveryId": "3nfp8xk1q2z7",
"event": "Grant.LimitExceeded",
"createdAt": "2024-01-01T00:00:00.000Z",
"ip": "192.168.0.1",
"userAgent": "Mozilla/5.0",
"userId": "u_001",
"applicationId": "app_xyz",
"application": {
"id": "app_xyz",
"type": "SPA",
"name": "My app",
"description": "My app description"
},
"maxAllowedGrants": 2,
"preRevocationActiveGrantCount": 3,
"revokedGrantIds": ["grant_001"]
}
傳送說明:
- 被撤銷的授權紀錄會在撤銷時一併刪除,因此
revokedGrantIds內的 ID 不再能透過授權查詢端點或 Console 查到——那些僅回傳有效授權。若需後續查詢,請自行保存這些 ID。 - 僅當實際有授權被撤銷時才會觸發事件。若授權未超過上限則不會產生事件。
- 每次授權超過上限都會觸發一次,因此使用者若不斷從超過允許數量的裝置登入,會產生多次事件。
- 傳送採 fire-and-forget:慢速或失敗的端點不會阻擋或影響使用者授權。失敗的傳送會如其他 webhook 一樣記錄於稽核日誌。