跳到主要内容

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。

通用字段

无论属于哪个类别,每次投递都会包含:

字段类型可选说明
hookIdstringLogto 中 webhook 配置的标识符。
eventstring触发本次投递的事件。
createdAtstring以 ISO 8601 格式表示的 payload 创建时间。
userAgentstring触发请求的 user-agent。

每个类别还会包含触发请求的 IP 地址:用户流程事件下字段名为 userIp,数据变更和异常事件下为 ip。语义一致,仅为历史兼容保留不同命名。

用户流程事件 payload

事件: PostRegister, PostSignIn, PostResetPassword

当用户完成由体验 (Experience) API 处理的注册、登录或重置密码流程时触发。除了通用字段外,请求体还包含:

字段类型可选说明
interactionEvent'SignIn' | 'Register' | 'ForgotPassword'用户流程事件类型。分别对应 PostSignIn / PostRegister / PostResetPassword。字段名保留历史 "interaction" 命名。
sessionIdstring本事件的 Session ID(非 Interaction ID),如适用。
userIpstring触发请求的 IP 地址。
userIdstring与本事件关联的用户 ID,如适用。
userUserEntity与本事件关联的用户实体,如适用。
applicationIdstring与本事件关联的应用 ID,如适用。
applicationApplicationEntity与本事件关联的应用实体,如适用。

实体结构

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 上下文,描述变更的触发方式。上下文根据触发来源有两种变体:
  • 一个事件特定 payload:受影响的实体在 data 字段中(部分事件还会有额外顶层字段)。详见事件特定数据 payload

体验 (Experience) API 上下文字段

当变更由体验 (Experience) API 上的用户端流程触发时出现,例如注册时的 User.Created 或资料更新时的 User.Data.Updated

字段类型可选说明
interactionEvent'SignIn' | 'Register' | 'ForgotPassword'产生变更的用户流程事件类型。字段名保留历史 "interaction" 命名。
sessionIdstring本事件的 Session ID(非 Interaction ID),如适用。
applicationIdstring应用 ID,如适用。
applicationApplicationEntity应用实体,如适用。

Management API 上下文字段

当变更由 Management API 调用触发时出现。

字段类型可选说明
pathstring触发本 webhook 的 API 调用路径。
methodstringAPI 调用的 HTTP 方法。
statusnumberAPI 调用的响应状态码。
paramsobjectAPI 调用的 koa 路径参数。
matchedRoutestringkoa 匹配到的路由。Logto 用于匹配已启用的 webhook 事件过滤器。

事件特定数据 payload

每个数据变更事件都包含顶层的 data 字段,携带受影响的实体;如果变更无法归纳为单一实体(如删除和成员变更事件),则为 null。部分事件还会有除 data 外的事件特定顶层字段,Organization.Membership.Updated 就是其中之一,见下文。

用户事件

事件字段类型可选说明
User.CreateddataUserEntity新创建的用户实体。
User.Data.UpdateddataUserEntity更新后的用户实体。
User.Deleteddatanull/

角色 (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.CreateddataRole新创建的角色实体。
Role.Data.UpdateddataRole更新后的角色实体。
Role.Deleteddatanull/
Role.Scopes.UpdateddataScope[]分配给该角色的更新后权限 (Scopes)。
Role.Scopes.UpdatedroleIdstring分配权限 (Scopes) 的角色 ID。(仅在通过预分配权限创建角色时提供。)

权限 (Scope) 事件

事件字段类型可选说明
Scope.CreateddataScope新创建的权限 (Scope) 实体。
Scope.Data.UpdateddataScope更新后的权限 (Scope) 实体。
Scope.Deleteddatanull/

组织 (Organization) 事件

type Organization = {
id: string;
name: string;
description?: string;
customData: object;
createdAt: number;
};
事件字段类型可选说明
Organization.CreateddataOrganization新创建的组织 (Organization) 实体。
Organization.Data.UpdateddataOrganization更新后的组织 (Organization) 实体。
Organization.Deleteddatanull/
Organization.Membership.Updateddatanull/变更通过可选顶层 delta 数组描述。见下文 Organization.Membership.Updated payload
Organization.Membership.Updated payload

除了通用字段和适用于触发来源的 API 上下文字段(Management API 路由用 Management API 上下文,即时供应用 体验 (Experience) API 上下文),Organization.Membership.Updated 事件还会在 payload 顶层(与 eventcreatedAt 等并列,不在 data 内,该事件下 data 始终为 null)携带 organizationId 及可选 delta 数组。

字段类型可选说明
organizationIdstring发生成员变更的组织 (Organization)。
addedUserIdsstring[]本次触发新增的用户 ID。未新增用户或不影响用户成员时省略。
removedUserIdsstring[]本次触发移除的用户 ID。未移除用户时省略。
addedApplicationIdsstring[]新增的应用 ID。未新增应用或不影响应用成员时省略。
removedApplicationIdsstring[]移除的应用 ID。未移除应用时省略。

这四个 delta 数组可选且增量:对于不关心它们的消费方不会改变现有 payload 结构,且历史的 data: null 字段依然保留。

触发方式与可能出现的 delta 字段
触发方式可能出现的 delta 字段
POST /organizations/:id/usersaddedUserIds
PUT /organizations/:id/usersaddedUserIds, removedUserIds
DELETE /organizations/:id/users/:userIdremovedUserIds
POST /organizations/:id/applicationsaddedApplicationIds
PUT /organizations/:id/applicationsaddedApplicationIds, removedApplicationIds
DELETE /organizations/:id/applications/:applicationIdremovedApplicationIds
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
) {
// 有实际成员变更,处理之
}

?.lengthundefined[] 都为假,因此无论字段缺失还是(假设未来)发空数组都能兼容。

示例 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.CreateddataOrganizationRole新创建的组织角色实体。
OrganizationRole.Data.UpdateddataOrganizationRole更新后的组织角色实体。
OrganizationRole.Deleteddatanull/
OrganizationRole.Scopes.Updateddatanull/
OrganizationRole.Scopes.UpdatedorganizationRoleIdstring分配权限 (Scopes) 的角色 ID。(仅在通过预分配权限创建角色时提供。)

组织权限 (scope) 事件

事件字段类型可选说明
OrganizationScope.CreateddataOrganizationScope新创建的组织权限 (scope) 实体。
OrganizationScope.Data.UpdateddataOrganizationScope更新后的组织权限 (scope) 实体。
OrganizationScope.Deleteddatanull/

异常事件 payload

事件: Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded

在安全事件发生时触发,例如连续验证失败后账户被锁定,或因应用并发设备数超限而撤销授权。

每个异常事件都包含通用字段和一个 ip 字段(结构同数据变更事件)。其余字段依事件而异。

Identifier.Lockout

来源于用户端流程,因此请求体还包含体验 (Experience) API 上下文字段,以及:

enum SignInIdentifier {
Email = 'email',
Phone = 'phone',
Username = 'username',
}
字段类型可选说明
typeSignInIdentifier用户的标识类型,如 email、phone 或 username。
valuestring触发锁定的用户标识值。

Message.RateLimited

来源于用户端流程,因此请求体还包含体验 (Experience) API 上下文字段,以及:

字段类型可选说明
actionstring被限流的操作,例如 VerificationCodeSend
recipientstring触发发送速率限制的邮箱地址或手机号。

Grant.LimitExceeded

当一次成功授权使用户超出应用的最大并发认证设备数限制(maxAllowedGrants)并导致 Logto 撤销其最早的授权时触发。

该事件由 OIDC 授权端点发出,而非体验 (Experience) API,因此不包含 interactionEventsessionId。除通用字段和 ip 外,请求体还包含:

字段类型可选说明
userIdstring被撤销授权的用户。
applicationIdstring超出 maxAllowedGrants 限制的应用。
applicationApplicationEntity应用实体。若投递时无法解析应用则省略。
maxAllowedGrantsnumber事件触发时应用配置的限制值。
preRevocationActiveGrantCountnumber撤销前用户在该应用下持有的活跃授权数(含本次新发放的)。
revokedGrantIdsstring[]实际被撤销的授权 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 一样记录在审计日志中。