본문으로 건너뛰기

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 호출의 경로입니다.
methodstring✅API 호출의 HTTP 메서드입니다.
statusnumber✅API 호출의 응답 상태 코드입니다.
paramsobject✅API 호출의 koa path params입니다.
matchedRoutestring✅koa의 매칭된 라우트입니다. 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
) {
// 실제 멤버십 변경, 처리 필요
}

?.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"]
}

기존 멤버 재추가, 무의미한 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 인가 엔드포인트에서 발생하므로, interactionEvent나 sessionId는 포함되지 않습니다. 공통 필드와 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과 마찬가지로 감사 로그에 기록됩니다.