Webhooks リクエスト
Webhook イベントが発火すると、Logto はそれにサブスクライブしているすべてのエンドポイントに POST リクエストを送信します。全イベントカタログは Webhooks イベント にあります。本ページでは、Logto が配信する リクエストの構造 について説明します。
リクエストヘッダー
| Key | カスタマイズ可 | 備考 |
|---|---|---|
| user-agent | ✅ | デフォルトは Logto (https://logto.io/)。 |
| content-type | ✅ | デフォルトは application/json。 |
| logto-signature-sha-256 | リクエストボディの署名。Webhook のセキュリティ保護 を参照。 |
カスタマイズ可能なヘッダーは、セキュア Webhook 設定で上書きできます。
リクエストボディの概要
ボディは JSON オブジェクトです。その正確な構造は、イベントが属するファミリーによって異なります:
| ファミリー | イベント | 発火タイミング |
|---|---|---|
| ユーザーフロー | PostRegister, PostSignIn, PostResetPassword | エンドユーザーが Experience API でサインアップ・サインイン・パスワードリセットフローを完了したとき。 |
| データ変更 | User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.* | Management API の呼び出しや Experience API 上のユーザーフローで基礎データモデルが変更されたとき。 |
| 例外 | Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded | セキュリティインシデント時。例:連続した認証失敗でアカウントがロックされた場合など。 |
すべてのファミリーは 共通フィールド を共有します。各ファミリーはさらに独自のリクエストコンテキストフィールドとイベント固有のペイロードを追加します。
共通フィールド
ファミリーに関係なくすべての配信に含まれるフィールド:
| フィールド | 型 | オプション | 備考 |
|---|---|---|---|
| hookId | string | Logto の Webhook 設定識別子。 | |
| event | string | この配信をトリガーしたイベント。 | |
| createdAt | string | ISO 8601 形式のペイロード作成時刻。 | |
| userAgent | string | ✅ | トリガーリクエストのユーザーエージェント。 |
各ファミリーはまた、トリガーリクエストの IP アドレスも含みます。ユーザーフローイベントでは userIp、データ変更・例外イベントでは ip というフィールド名です。意味は同じですが、後方互換性のため名前の違いが維持されています。
ユーザーフローイベントのペイロード
イベント: PostRegister, PostSignIn, PostResetPassword
エンドユーザーが Experience API でサインアップ・サインイン・パスワードリセットフローを完了したときに発火します。共通フィールド に加え、ボディには以下が含まれます:
| フィールド | 型 | オプション | 備考 |
|---|---|---|---|
| interactionEvent | 'SignIn' | 'Register' | 'ForgotPassword' | ユーザーフローイベントタイプ。PostSignIn / PostRegister / PostResetPassword にそれぞれ対応。フィールド名は歴史的な "interaction" を保持。 | |
| sessionId | string | ✅ | このイベントのセッション 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;
};
全フィールドリファレンスは ユーザー および アプリケーション を参照してください。
データ変更イベントのペイロード
イベント: User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.* 配下のすべてのイベント。Webhooks イベント → データ変更 Webhook イベント で全カタログを確認できます。
ボディには常に以下が含まれます:
- 共通フィールド
ipフィールド(トリガーリクエストの IP アドレス。オプション、判明している場合のみ)- API コンテキスト(変更がどのようにトリガーされたかを記述)。トリガー元により 2 種類のいずれか:
- Experience API コンテキスト:ユーザー向けフローからの変更時
- Management API コンテキスト:Management API からの直接呼び出し時
- イベント固有のペイロード:
dataに影響を受けたエンティティ、または一部イベントでは追加のトップレベルフィールド。イベント固有データペイロード を参照
Experience API コンテキストフィールド
Experience API 上のユーザー向けフロー(例:サインアップ時の User.Created やプロフィール更新時の User.Data.Updated)でトリガーされた場合に含まれます。
| フィールド | 型 | オプション | 備考 |
|---|---|---|---|
| interactionEvent | 'SignIn' | 'Register' | 'ForgotPassword' | ✅ | 変更を生じさせたユーザーフローイベントタイプ。フィールド名は歴史的な "interaction" を保持。 |
| sessionId | string | ✅ | このイベントのセッション 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 イベントフィルターを判別。 |
イベント固有データペイロード
すべてのデータ変更イベントは、影響を受けたエンティティを持つトップレベルの data フィールド(削除やメンバーシップイベントなど単一エンティティで要約できない場合は null)を含みます。一部イベントは data 以外にもトップレベルフィールドを持ちます。Organization.Membership.Updated がその一例で、下記で説明します。
ユーザーイベント
| イベント | フィールド | 型 | オプション | 備考 |
|---|---|---|---|---|
| User.Created | data | UserEntity | 作成されたユーザーエンティティ。 | |
| User.Data.Updated | data | UserEntity | 更新されたユーザーエンティティ。 | |
| User.Deleted | data | null | / |
ロールイベント
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[] | ロールに割り当てられた更新後のスコープ。 | |
| Role.Scopes.Updated | roleId | string | ✅ | スコープが割り当てられたロール ID。(事前割り当てスコープ付きロール作成時のみ利用可能) |
権限(スコープ)イベント
| イベント | フィールド | 型 | オプション | 備考 |
|---|---|---|---|---|
| Scope.Created | data | Scope | 作成されたスコープエンティティ。 | |
| Scope.Data.Updated | data | Scope | 更新されたスコープエンティティ。 | |
| Scope.Deleted | data | null | / |
組織イベント
type Organization = {
id: string;
name: string;
description?: string;
customData: object;
createdAt: number;
};
| イベント | フィールド | 型 | オプション | 備考 |
|---|---|---|---|---|
| Organization.Created | data | Organization | 作成された組織エンティティ。 | |
| Organization.Data.Updated | data | Organization | 更新された組織エンティティ。 | |
| Organization.Deleted | data | null | / | |
| Organization.Membership.Updated | data | null | / | 変更内容はトップレベルのデルタ配列で記述。Organization.Membership.Updated ペイロード を参照。 |
Organization.Membership.Updated ペイロード
共通フィールド とトリガー元に応じた API コンテキストフィールド(Management API ルートなら Management API コンテキスト、ジャストインタイムプロビジョニングなら Experience API コンテキスト)に加え、Organization.Membership.Updated イベントは organizationId と、トップレベル(event や createdAt などと同階層、data の中ではなく)にオプションのデルタ配列を持ちます(このイベントの data は常に null)。
| フィールド | 型 | オプション | 備考 |
|---|---|---|---|
| organizationId | string | メンバーシップが変更された組織。 | |
| addedUserIds | string[] | ✅ | このトリガーで新たに追加されたユーザー ID。追加がなければ省略。ユーザーメンバーシップに影響しない場合も省略。 |
| removedUserIds | string[] | ✅ | このトリガーで削除されたユーザー ID。削除がなければ省略。 |
| addedApplicationIds | string[] | ✅ | 新たに追加されたアプリケーション ID。追加がなければ省略。アプリケーションメンバーシップに影響しない場合も省略。 |
| removedApplicationIds | string[] | ✅ | 削除されたアプリケーション ID。削除がなければ省略。 |
4 つのデルタ配列は オプションかつ加算的 です。これらを期待しないコンシューマーの既存ペイロード構造を壊さず、従来の data: null フィールドもそのまま出力されます。
トリガーと出力される可能性のあるデルタフィールド
| トリガー | 出力される可能性のあるデルタフィールド |
|---|---|
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 |
空のデルタは省略(省略 = 変更なし)
空のデルタ配列は 完全に省略 されます。例えば、PUT /organizations/:id/users で既存セットと同じメンバーシップに置き換えた場合、実質的な変更がないためペイロードは { organizationId } だけになり、4 つのデルタフィールドはすべて省略されます。同様に、既存メンバーの再追加や既存メンバーによる招待再受諾も同じです。
コンシューマーは、フィールドが存在しない場合は「その側に変更なし」とみなす必要があります。「空の変更」とはみなさないでください。
配列ごとの上限(サイレント切り捨て)
各デルタ配列は 最大 5000 件 で上限されます。1 回の Management API 呼び出しで 5000 件を超えるユーザー(またはアプリケーション)を追加・削除した場合、該当配列は最初の 5000 件にサイレントで切り捨てられます。ペイロード内に上限発動のマーカーはありません。
管理者向け一括操作で 1 回の呼び出しで 5000 件超のメンバーに影響する可能性がある場合、配列がちょうど 5000 件なら Management API で正規メンバーシップを再取得してください:
GET /organizations/:id/users:全ユーザーメンバーシップGET /organizations/:id/applications:全アプリケーションメンバーシップ
これは GitHub の push イベントが commits を 20 件で上限し、完全リストは compare API で取得するパターンと同じです。
No-op イベントのスキップ
コンシューマー側で no-op 配信(デルタフィールドなしイベント)をスキップするには、デルタ配列の存在でフィルタしてください:
if (
payload.addedUserIds?.length ||
payload.removedUserIds?.length ||
payload.addedApplicationIds?.length ||
payload.removedApplicationIds?.length
) {
// 実際のメンバーシップ変更があった場合のみ処理
}
?.length は undefined と [] の両方で falsy になるため、フィールドが存在しない場合も(将来的に空配列で出力される場合も)この判定で安全です。
ペイロード例
ユーザー追加(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"]
}
既存メンバーの再追加、no-op の 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 | ✅ | スコープが割り当てられたロール ID。(事前割り当てスコープ付きロール作成時のみ利用可能) |
組織権限(スコープ)イベント
| イベント | フィールド | 型 | オプション | 備考 |
|---|---|---|---|---|
| OrganizationScope.Created | data | OrganizationScope | 作成された組織スコープエンティティ。 | |
| OrganizationScope.Data.Updated | data | OrganizationScope | 更新された組織スコープエンティティ。 | |
| OrganizationScope.Deleted | data | null | / |
例外イベントのペイロード
イベント: 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
認可 (Authorization) 成功時に、ユーザーがアプリの 最大同時認証デバイス数(maxAllowedGrants)を超えた場合に発火し、Logto がそのアプリの最も古いグラントを破棄します。
このイベントは OIDC 認可 (Authorization) エンドポイントから発火するため、interactionEvent や sessionId は含まれません。共通フィールドと ip に加え、ボディには以下が含まれます:
| フィールド | 型 | オプション | 備考 |
|---|---|---|---|
| userId | string | グラントが破棄されたユーザー。 | |
| applicationId | string | maxAllowedGrants 上限を超えたアプリケーション。 | |
| application | ApplicationEntity | ✅ | アプリケーションエンティティ。配信時に解決できない場合は省略。 |
| maxAllowedGrants | number | イベント発火時にアプリケーションに設定されていた上限。 | |
| preRevocationActiveGrantCount | number | 破棄前にこのアプリでユーザーが保持していたアクティブグラント数(今回発行分も含む)。 | |
| revokedGrantIds | string[] | 実際に破棄されたグラント ID(古い順)。 |
ペイロード例:
{
"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 はグラント一覧エンドポイントやコンソールでは アクティブ グラントのみ返されます。必要ならペイロードを記録してください。 - 実際に 1 件以上のグラントが 破棄された場合のみ イベントが発火します。上限内であればイベントは発生しません。
- 上限超過のたびに発火するため、許可された台数を超えて何度もサインインすると、その都度イベントが発生します。
- 配信は fire-and-forget 方式です。遅延や失敗したエンドポイントでもユーザーの認可 (Authorization) をブロックしません。失敗した配信も他の Webhook と同様に監査ログに記録されます。