본문으로 건너뛰기

Webhook 요청

Webhook 이벤트가 발생하면, Logto는 해당 이벤트에 구독된 모든 엔드포인트에 POST 요청을 보냅니다. 전체 이벤트 카탈로그는 Webhook 이벤트에서 확인할 수 있습니다. 이 페이지에서는 Logto가 전달하는 요청의 형태를 문서화합니다.

요청 헤더

KeyCustomizableNotes
user-agent기본값은 Logto (https://logto.io/) 입니다.
content-type기본값은 application/json 입니다.
logto-signature-sha-256요청 본문의 서명입니다. Webhook 보안 설정 참고.

커스터마이즈 가능한 헤더는 보안 webhook 설정을 통해 오버라이드할 수 있습니다.

요청 본문 개요

본문은 JSON 객체입니다. 정확한 형태는 이벤트가 속한 패밀리에 따라 다릅니다:

FamilyEventsWhen it fires
사용자 플로우PostRegister, PostSignIn, PostResetPassword사용자가 Experience API에서 회원가입, 로그인, 비밀번호 재설정 플로우를 완료할 때 발생합니다.
데이터 변경User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.*Management API 호출 또는 Experience API의 사용자 플로우로 데이터 모델이 변경될 때 발생합니다.
예외Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded보안 사고 발생 시, 예를 들어 연속된 인증 실패로 계정이 잠긴 경우 등.

모든 패밀리는 공통 필드 집합을 공유합니다. 각 패밀리는 여기에 자체적인 요청 컨텍스트 필드와 이벤트별 페이로드를 추가합니다.

공통 필드

패밀리와 관계없이 모든 전달에 포함됩니다:

FieldTypeOptionalNotes
hookIdstringLogto의 webhook 구성 식별자입니다.
eventstring이 전달을 트리거한 이벤트입니다.
createdAtstringISO 8601 형식의 페이로드 생성 시간입니다.
userAgentstring트리거 요청의 user-agent입니다.

각 패밀리에는 트리거 요청의 IP 주소도 포함됩니다. 사용자 플로우 이벤트에서는 userIp, 데이터 변경 및 예외 이벤트에서는 ip 필드명으로 제공됩니다. 의미는 동일하며, 과거 호환성을 위해 이름만 다릅니다.

사용자 플로우 이벤트 페이로드

이벤트: PostRegister, PostSignIn, PostResetPassword.

사용자가 Experience API에서 회원가입, 로그인, 비밀번호 재설정 플로우를 완료할 때 발생합니다. 공통 필드 외에, 본문에는 다음이 포함됩니다:

FieldTypeOptionalNotes
interactionEvent'SignIn' | 'Register' | 'ForgotPassword'사용자 플로우 이벤트 타입입니다. 각각 PostSignIn / PostRegister / PostResetPassword에 매핑됩니다. 필드명은 과거 "interaction" 명칭을 유지합니다.
sessionIdstring해당 이벤트의 세션 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;
};

전체 필드 참조는 사용자애플리케이션 문서를 참고하세요.

데이터 변경 이벤트 페이로드

이벤트: User.*, Role.*, Scope.*, Organization.*, OrganizationRole.*, OrganizationScope.* 하위의 모든 이벤트. 전체 카탈로그는 Webhook 이벤트 → 데이터 변경 webhook 이벤트에서 확인하세요.

본문에는 항상 다음이 포함됩니다:

  • 공통 필드
  • 트리거 요청의 IP 주소를 담는 ip 필드 (선택적, 알려진 경우에만 포함)
  • 변경이 어떻게 트리거되었는지 설명하는 API 컨텍스트. 트리거 소스에 따라 두 가지 중 하나입니다:
  • 이벤트별 페이로드: data에 영향을 받은 엔티티, 그리고 (일부 이벤트의 경우) 추가 최상위 필드. 이벤트별 데이터 페이로드 참고.

Experience API 컨텍스트 필드

Experience API의 사용자 플로우에서 변경이 트리거된 경우에 포함됩니다. 예: 회원가입 중 User.Created, 프로필 업데이트 중 User.Data.Updated 등.

FieldTypeOptionalNotes
interactionEvent'SignIn' | 'Register' | 'ForgotPassword'변경을 발생시킨 사용자 플로우 이벤트 타입. 필드명은 과거 "interaction" 명칭을 유지합니다.
sessionIdstring해당 이벤트의 세션 ID (Interaction ID 아님), 해당되는 경우에만 포함됩니다.
applicationIdstring해당되는 경우 애플리케이션 ID.
applicationApplicationEntity해당되는 경우 애플리케이션 엔티티.

Management API 컨텍스트 필드

Management API 호출로 변경이 트리거된 경우에 포함됩니다.

FieldTypeOptionalNotes
pathstring이 webhook을 트리거한 API 호출의 경로입니다.
methodstringAPI 호출의 HTTP 메서드입니다.
statusnumberAPI 호출의 응답 상태 코드입니다.
paramsobjectAPI 호출의 koa path params입니다.
matchedRoutestringkoa의 매칭된 라우트입니다. Logto는 이 필드를 사용해 활성화된 webhook 이벤트 필터와 매칭합니다.

이벤트별 데이터 페이로드

모든 데이터 변경 이벤트에는 영향을 받은 엔티티를 담는 최상위 data 필드가 포함되며, 단일 엔티티로 요약할 수 없는 경우(삭제 및 멤버십 이벤트 등)에는 null이 됩니다. 일부 이벤트는 data 외에 추가 최상위 필드를 포함할 수 있습니다. Organization.Membership.Updated가 그 예로, 아래에 문서화되어 있습니다.

사용자 이벤트

EventFieldTypeOptionalNotes
User.CreateddataUserEntity생성된 사용자 엔티티.
User.Data.UpdateddataUserEntity업데이트된 사용자 엔티티.
User.Deleteddatanull/

역할 이벤트

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;
};
EventFieldTypeOptionalNotes
Role.CreateddataRole생성된 역할 엔티티.
Role.Data.UpdateddataRole업데이트된 역할 엔티티.
Role.Deleteddatanull/
Role.Scopes.UpdateddataScope[]역할에 할당된 업데이트된 스코프.
Role.Scopes.UpdatedroleIdstring스코프가 할당된 역할 ID. (사전 할당된 스코프로 역할을 생성할 때만 제공)

권한 (스코프) 이벤트

EventFieldTypeOptionalNotes
Scope.CreateddataScope생성된 스코프 엔티티.
Scope.Data.UpdateddataScope업데이트된 스코프 엔티티.
Scope.Deleteddatanull/

조직 이벤트

type Organization = {
id: string;
name: string;
description?: string;
customData: object;
createdAt: number;
};
EventFieldTypeOptionalNotes
Organization.CreateddataOrganization생성된 조직 엔티티.
Organization.Data.UpdateddataOrganization업데이트된 조직 엔티티.
Organization.Deleteddatanull/
Organization.Membership.Updateddatanull/변경 사항은 선택적 최상위 델타 배열로 설명됩니다. 아래 Organization.Membership.Updated 페이로드 참고.
Organization.Membership.Updated 페이로드

공통 필드 및 트리거 소스에 해당하는 API 컨텍스트 필드(Management API 컨텍스트 또는 Experience API 컨텍스트) 외에, Organization.Membership.Updated 이벤트는 organizationId와 선택적 델타 배열을 페이로드의 최상위에 포함합니다 (event, createdAt 등과 나란히, 항상 data 내부가 아닌, 이 이벤트의 경우 data는 항상 null).

FieldTypeOptionalNotes
organizationIdstring멤버십이 변경된 조직의 ID.
addedUserIdsstring[]이번 트리거로 새로 추가된 사용자 ID. 추가된 사용자가 없거나, 트리거가 사용자 멤버십에 영향을 주지 않으면 생략됩니다.
removedUserIdsstring[]이번 트리거로 제거된 사용자 ID. 제거된 사용자가 없으면 생략됩니다.
addedApplicationIdsstring[]새로 추가된 애플리케이션 ID. 추가된 애플리케이션이 없거나, 트리거가 애플리케이션 멤버십에 영향을 주지 않으면 생략됩니다.
removedApplicationIdsstring[]제거된 애플리케이션 ID. 제거된 애플리케이션이 없으면 생략됩니다.

네 가지 델타 배열은 선택적이며 추가적입니다. 즉, 이 배열이 없는 경우에도 기존 페이로드 형태는 변하지 않으며, 레거시 data: null 필드는 그대로 유지됩니다.

트리거별 델타 필드 예시
TriggerPossible delta fields
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
Just-in-time provisioning when adding the user to a new organizationaddedUserIds
빈 델타는 생략됨 (없음 ≠ 빈 변경)

빈 델타 배열은 페이로드에서 완전히 생략됩니다. 예를 들어, 기존 멤버십과 동일한 집합으로 교체하는 PUT /organizations/:id/users는 실제 변경이 없으므로 페이로드는 { organizationId }만 남고 네 가지 델타 필드는 모두 빠집니다. 이미 멤버인 사용자를 다시 추가하거나 이미 멤버인 사용자가 초대를 다시 수락하는 경우도 마찬가지입니다.

컨슈머는 필드가 없는 경우 "해당 측면에 변경 없음"으로 간주해야 하며, "빈 변경"으로 해석해서는 안 됩니다.

배열별 최대값 (조용한 잘림)

각 델타 배열은 최대 5000개 항목으로 제한됩니다. 한 번의 Management API 호출로 5000명(또는 애플리케이션) 이상을 추가/제거하면 해당 델타 배열은 처음 5000개 항목까지만 조용히 잘립니다. 페이로드 내에 잘림 여부를 알리는 마커는 없습니다.

관리자 대량 작업이 한 번에 5000명 이상의 멤버에 영향을 줄 수 있다면, 배열 길이가 정확히 5000개일 때 Management API로 멤버십을 재조정해야 합니다:

  • GET /organizations/:id/users: 전체 사용자 멤버십
  • GET /organizations/:id/applications: 전체 애플리케이션 멤버십

이 패턴은 GitHub의 push 이벤트(커밋 20개 제한, 전체 목록은 compare API로 안내)와 동일합니다.

무의미(no-op) 이벤트 건너뛰기

컨슈머 측에서 무의미한(no-op) 전달(델타 필드가 없는 이벤트)을 건너뛰려면, 델타 배열 존재 여부로 필터링하세요:

if (
payload.addedUserIds?.length ||
payload.removedUserIds?.length ||
payload.addedApplicationIds?.length ||
payload.removedApplicationIds?.length
) {
// 실제 멤버십 변경, 처리 필요
}

?.lengthundefined[] 모두에 대해 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"]
}

기존 멤버 재추가, 무의미한 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;
};
EventFieldTypeOptionalNotes
OrganizationRole.CreateddataOrganizationRole생성된 조직 역할 엔티티.
OrganizationRole.Data.UpdateddataOrganizationRole업데이트된 조직 역할 엔티티.
OrganizationRole.Deleteddatanull/
OrganizationRole.Scopes.Updateddatanull/
OrganizationRole.Scopes.UpdatedorganizationRoleIdstring스코프가 할당된 역할 ID. (사전 할당된 스코프로 역할을 생성할 때만 제공)

조직 권한(스코프) 이벤트

EventFieldTypeOptionalNotes
OrganizationScope.CreateddataOrganizationScope생성된 조직 스코프 엔티티.
OrganizationScope.Data.UpdateddataOrganizationScope업데이트된 조직 스코프 엔티티.
OrganizationScope.Deleteddatanull/

예외 이벤트 페이로드

이벤트: Identifier.Lockout, Message.RateLimited, Grant.LimitExceeded.

보안 사고 발생 시, 예를 들어 연속된 인증 실패로 계정이 잠기거나, 앱의 동시 인증 기기 제한을 초과해 grant가 회수될 때 발생합니다.

모든 예외 이벤트는 공통 필드ip 필드(데이터 변경 이벤트와 동일한 형태)를 포함합니다. 나머지 필드는 이벤트에 따라 다릅니다.

Identifier.Lockout

사용자 플로우에서 발생하므로, 본문에는 Experience API 컨텍스트 필드도 포함되며, 추가로:

enum SignInIdentifier {
Email = 'email',
Phone = 'phone',
Username = 'username',
}
FieldTypeOptionalNotes
typeSignInIdentifier사용자의 식별자 타입(이메일, 전화번호, 사용자명 등).
valuestring잠금이 발생한 사용자의 식별자 값.

Message.RateLimited

사용자 플로우에서 발생하므로, 본문에는 Experience API 컨텍스트 필드도 포함되며, 추가로:

FieldTypeOptionalNotes
actionstring제한된 액션, 예: VerificationCodeSend.
recipientstring발송 속도 제한에 걸린 이메일 주소 또는 전화번호.

Grant.LimitExceeded

성공적인 인가 (Authorization)로 인해 사용자가 앱의 최대 동시 인증 기기 수 제한(maxAllowedGrants)을 초과하면 Logto가 해당 앱의 가장 오래된 grant를 회수하며 발생합니다.

이 이벤트는 Experience API가 아닌 OIDC 인가 엔드포인트에서 발생하므로, interactionEventsessionId는 포함되지 않습니다. 공통 필드와 ip 외에, 본문에는 다음이 포함됩니다:

FieldTypeOptionalNotes
userIdstringgrant가 회수된 사용자 ID.
applicationIdstring제한을 초과한 애플리케이션 ID.
applicationApplicationEntity애플리케이션 엔티티. 전달 시점에 애플리케이션을 확인할 수 없으면 생략됩니다.
maxAllowedGrantsnumber이벤트 발생 시 애플리케이션에 설정된 제한값.
preRevocationActiveGrantCountnumber회수 전 사용자가 해당 애플리케이션에 보유한 활성 grant 수(방금 발급된 것 포함).
revokedGrantIdsstring[]실제로 회수된 grant의 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"]
}

전달 참고 사항:

  • 회수된 grant 레코드는 회수와 함께 삭제되므로, revokedGrantIds의 ID는 grant 목록 엔드포인트나 콘솔에서 더 이상 조회되지 않습니다(이들은 활성 grant만 반환). 페이로드 자체를 기록으로 삼고, 나중에 필요하다면 별도로 저장하세요.
  • 실제로 하나 이상의 grant가 회수될 때만 이벤트가 발생합니다. 제한 내에서 인가가 이루어지면 이벤트가 발생하지 않습니다.
  • 제한을 초과하는 인가가 있을 때마다 이벤트가 발생하므로, 사용자가 허용된 기기 수보다 더 많은 기기로 반복 로그인하면 퇴출(eviction)마다 이벤트가 발생합니다.
  • 전달은 fire-and-forget 방식입니다. 느리거나 실패하는 엔드포인트가 있어도 사용자의 인가가 차단되거나 실패하지 않습니다. 실패한 전달도 다른 webhook과 마찬가지로 감사 로그에 기록됩니다.