Webhooks 请求
当一个 webhook 事件被触发时,Logto 会向每个已订阅该事件的端点发送一个 POST 请求。完整的事件目录见 Webhooks 事件;本页记录了 Logto 发送的请求结构。
请求头
| Key | 可自定义 | 说明 |
|---|---|---|
| user-agent | ✅ | 默认值为 Logto (https://logto.io/)。 |
| content-type | ✅ | 默认值为 application/json。 |
| logto-signature-sha-256 | 请求体的签名。详见 保护你的 webhooks。 |
可自定义的请求头可以通过 安全 webhook 配置进行覆盖。
请求体概览
请求体是一个 JSON 对象。其具体结构取决于事件所属的类别:
| 类别 | 事件 | 触发时机 |
|---|---|---|
| 用户流程 | PostRegister, PostSignIn, PostResetPassword | 用户完成由体验 (Experience) API 处理的注册、登录或重置密码流程时触发。 |
| 数据变更 | User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.* | 通过 Management API 调用或体验 (Experience) API 上的用户流程导致底层数据模型发生变更时触发。 |
| 异常 | Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded | 安全事件,例如连续验证失败后账户被锁定等。 |
每个类别都包含一组通用字段。每个类别还会叠加自身的请求上下文字段和事件特定的 payload。
通用字段
无论属于哪个类别,每次投递都会包含:
| 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|
| hookId | string | Logto 中 webhook 配置的标识符。 | |
| event | string | 触发本次投递的事件。 | |
| createdAt | string | 以 ISO 8601 格式表示的 payload 创建时间。 | |
| userAgent | string | ✅ | 触发请求的 user-agent。 |
每个类别还会包含触发请求的 IP 地址:用户流程事件下字段名为 userIp,数据变更和异常事件下为 ip。语义一致,仅为历史兼容保留不同命名。
用户流程事件 payload
事件: PostRegister, PostSignIn, PostResetPassword。
当用户完成由体验 (Experience) API 处理的注册、登录或重置密码流程时触发。除了通用字段外,请求体还包含:
| 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|
| interactionEvent | 'SignIn' | 'Register' | 'ForgotPassword' | 用户流程事件类型。分别对应 PostSignIn / PostRegister / PostResetPassword。字段名保留历史 "interaction" 命名。 | |
| sessionId | string | ✅ | 本事件的 Session ID(非 Interaction ID),如适用。 |
| userIp | string | ✅ | 触发请求的 IP 地址。 |
| userId | string | ✅ | 与本事件关联的用户 ID,如适用。 |
| user | UserEntity | ✅ | 与本事件关联的用户实体,如适用。 |
| applicationId | string | ✅ | 与本事件关联的应用 ID,如适用。 |
| application | ApplicationEntity | ✅ | 与本事件关联的应用实体,如适用。 |
实体结构
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
事件: 所有 User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.* 下的事件。完整目录见 Webhooks 事件 → 数据变更 webhook 事件。
请求体始终包含:
- 通用字段。
- 一个
ip字段,表示触发请求的 IP 地址(可选,已知时提供)。 - 一个API 上下文,描述变更的触发方式。上下文根据触发来源有两种变体:
- 体验 (Experience) API 上下文:变更来自用户端流程时。
- Management API 上下文:变更来自直接的 Management API 调用时。
- 一个事件特定 payload:受影响的实体在
data字段中(部分事件还会有额外顶层字段)。详见事件特定数据 payload。
体验 (Experience) API 上下文字段
当变更由体验 (Experience) API 上的用户端流程触发时出现,例如注册时的 User.Created 或资料更新时的 User.Data.Updated。
| 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|
| interactionEvent | 'SignIn' | 'Register' | 'ForgotPassword' | ✅ | 产生变更的用户流程事件类型。字段名保留历史 "interaction" 命名。 |
| sessionId | string | ✅ | 本事件的 Session ID(非 Interaction ID),如适用。 |
| applicationId | string | ✅ | 应用 ID,如适用。 |
| application | ApplicationEntity | ✅ | 应用实体,如适用。 |
Management API 上下文字段
当变更由 Management API 调用触发时出现。
| 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|
| path | string | ✅ | 触发本 webhook 的 API 调用路径。 |
| method | string | ✅ | API 调用的 HTTP 方法。 |
| status | number | ✅ | API 调用的响应状态码。 |
| params | object | ✅ | API 调用的 koa 路径参数。 |
| matchedRoute | string | ✅ | koa 匹配到的路由。Logto 用于匹配已启用的 webhook 事件过滤器。 |
事件特定数据 payload
每个数据变更事件都包含顶层的 data 字段,携带受影响的实体;如果变更无法归纳为单一实体(如删除和成员变更事件),则为 null。部分事件还会有除 data 外的事件特定顶层字段,Organization.Membership.Updated 就是其中之一,见下文。
用户事件
| 事件 | 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|---|
| User.Created | data | UserEntity | 新创建的用户实体。 | |
| User.Data.Updated | data | UserEntity | 更新后的用户实体。 | |
| User.Deleted | data | null | / |
角色 (Role) 事件
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;
};
| 事件 | 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|---|
| Role.Created | data | Role | 新创建的角色实体。 | |
| Role.Data.Updated | data | Role | 更新后的角色实体。 | |
| Role.Deleted | data | null | / | |
| Role.Scopes.Updated | data | Scope[] | 分配给该角色的更新后权限 (Scopes)。 | |
| Role.Scopes.Updated | roleId | string | ✅ | 分配权限 (Scopes) 的角色 ID。(仅在通过预分配权限创建角色时提供。) |
权限 (Scope) 事件
| 事件 | 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|---|
| Scope.Created | data | Scope | 新创建的权限 (Scope) 实体。 | |
| Scope.Data.Updated | data | Scope | 更新后的权限 (Scope) 实体。 | |
| Scope.Deleted | data | null | / |
组织 (Organization) 事件
type Organization = {
id: string;
name: string;
description?: string;
customData: object;
createdAt: number;
};
| 事件 | 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|---|
| Organization.Created | data | Organization | 新创建的组织 (Organization) 实体。 | |
| Organization.Data.Updated | data | Organization | 更新后的组织 (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 内,该事件下 data 始终为 null)携带 organizationId 及可选 delta 数组。
| 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|
| organizationId | string | 发生成员变更的组织 (Organization)。 | |
| addedUserIds | string[] | ✅ | 本次触发新增的用户 ID。未新增用户或不影响用户成员时省略。 |
| removedUserIds | string[] | ✅ | 本次触发移除的用户 ID。未移除用户时省略。 |
| addedApplicationIds | string[] | ✅ | 新增的应用 ID。未新增应用或不影响应用成员时省略。 |
| removedApplicationIds | string[] | ✅ | 移除的应用 ID。未移除应用时省略。 |
这四个 delta 数组可选且增量:对于不关心它们的消费方不会改变现有 payload 结构,且历史的 data: null 字段依然保留。
触发方式与可能出现的 delta 字段
| 触发方式 | 可能出现的 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 会被省略(缺失 ≠ 空变更)
空的 delta 数组会完全省略在 payload 中。例如,PUT /organizations/:id/users 用现有成员集替换成员集时没有实际变更,payload 只剩 { organizationId },四个 delta 字段都缺失。重新添加已存在成员、已是成员的用户再次接受邀请也同理。
消费方必须将缺失字段视为“该侧无变更”,而不是“空变更”。
单数组上限(静默截断)
每个 delta 数组上限为5000 条。当一次 Management API 调用在单次操作中新增或移除超过 5000 个用户(或应用)时,相应的 delta 数组会被静默截断为前 5000 条。payload 内不会有任何标记说明被截断。
如果你的应用会进行可能影响超过 5000 个成员的批量管理操作,看到数组正好为 5000 条时应主动通过 Management API 对成员进行权威同步:
GET /organizations/:id/users:获取完整用户成员列表。GET /organizations/:id/applications:获取完整应用成员列表。
这与 GitHub 的 push 事件类似,commits 上限 20 条,完整列表需用 compare API 获取。
跳过无操作事件
要在消费方跳过无操作(无 delta 字段)投递,可按 delta 数组是否存在过滤:
if (
payload.addedUserIds?.length ||
payload.removedUserIds?.length ||
payload.addedApplicationIds?.length ||
payload.removedApplicationIds?.length
) {
// 有实际成员变更,处理之
}
?.length 对 undefined 和 [] 都为假,因此无论字段缺失还是(假设未来)发空数组都能兼容。
示例 payload
添加用户(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)同步。
组织角色事件
type OrganizationRole = {
id: string;
name: string;
description?: string;
};
type OrganizationScope = {
id: string;
name: string;
description?: string;
};
| 事件 | 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|---|
| OrganizationRole.Created | data | OrganizationRole | 新创建的组织角色实体。 | |
| OrganizationRole.Data.Updated | data | OrganizationRole | 更新后的组织角色实体。 | |
| OrganizationRole.Deleted | data | null | / | |
| OrganizationRole.Scopes.Updated | data | null | / | |
| OrganizationRole.Scopes.Updated | organizationRoleId | string | ✅ | 分配权限 (Scopes) 的角色 ID。(仅在通过预分配权限创建角色时提供。) |
组织权限 (scope) 事件
| 事件 | 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|---|
| OrganizationScope.Created | data | OrganizationScope | 新创建的组织权限 (scope) 实体。 | |
| OrganizationScope.Data.Updated | data | OrganizationScope | 更新后的组织权限 (scope) 实体。 | |
| OrganizationScope.Deleted | data | null | / |
异常事件 payload
事件: Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded。
在安全事件发生时触发,例如连续验证失败后账户被锁定,或因应用并发设备数超限而撤销授权。
每个异常事件都包含通用字段和一个 ip 字段(结构同数据变更事件)。其余字段依事件而异。
Identifier.Lockout
来源于用户端流程,因此请求体还包含体验 (Experience) API 上下文字段,以及:
enum SignInIdentifier {
Email = 'email',
Phone = 'phone',
Username = 'username',
}
| 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|
| type | SignInIdentifier | 用户的标识类型,如 email、phone 或 username。 | |
| value | string | 触发锁定的用户标识值。 |
Message.RateLimited
来源于用户端流程,因此请求体还包含体验 (Experience) API 上下文字段,以及:
| 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|
| action | string | 被限流的操作,例如 VerificationCodeSend。 | |
| recipient | string | 触发发送速率限制的邮箱地址或手机号。 |
Grant.LimitExceeded
当一次成功授权使用户超出应用的最大并发认证设备数限制(maxAllowedGrants)并导致 Logto 撤销其最早的授权时触发。
该事件由 OIDC 授权端点发出,而非体验 (Experience) API,因此不包含 interactionEvent 或 sessionId。除通用字段和 ip 外,请求体还包含:
| 字段 | 类型 | 可选 | 说明 |
|---|---|---|---|
| userId | string | 被撤销授权的用户。 | |
| applicationId | string | 超出 maxAllowedGrants 限制的应用。 | |
| application | ApplicationEntity | ✅ | 应用实体。若投递时无法解析应用则省略。 |
| maxAllowedGrants | number | 事件触发时应用配置的限制值。 | |
| preRevocationActiveGrantCount | number | 撤销前用户在该应用下持有的活跃授权数(含本次新发放的)。 | |
| revokedGrantIds | string[] | 实际被撤销的授权 ID,按最早顺序排列。 |
示例 payload:
{
"hookId": "hook_abc",
"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 无法再通过授权列表接口或控制台查询——这些接口只返回活跃授权。如需后续使用,请自行持久化这些 ID。 - 仅当实际有授权被撤销时才会触发该事件。未超限的授权不会产生事件。
- 每次授权超限都会触发一次事件,因此用户多设备重复登录会产生多次撤销事件。
- 投递为“即发即弃”:慢速或失败的端点不会阻塞或导致用户授权失败。失败的投递会像其他 webhook 一样记录在审计日志中。